<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: UNITED UAV Official</title>
    <description>The latest articles on DEV Community by UNITED UAV Official (@united_uav).</description>
    <link>https://dev.to/united_uav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4098805%2F139c6fc1-2032-4e3f-b48d-f5dce516f55e.png</url>
      <title>DEV Community: UNITED UAV Official</title>
      <link>https://dev.to/united_uav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/united_uav"/>
    <language>en</language>
    <item>
      <title>UA30 Agricultural Drone: Auditing Application Rate from Flow, Speed, and Swath</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:31:40 +0000</pubDate>
      <link>https://dev.to/united_uav/ua30-agricultural-drone-auditing-application-rate-from-flow-speed-and-swath-2617</link>
      <guid>https://dev.to/united_uav/ua30-agricultural-drone-auditing-application-rate-from-flow-speed-and-swath-2617</guid>
      <description>&lt;p&gt;A spraying drone can follow its route accurately while its treatment report remains wrong. The usual failure is a mismatch between three quantities: liquid leaving the aircraft, distance travelled over the ground, and the width that actually counts as treated. A dashboard that displays only tank capacity cannot resolve that mismatch.&lt;/p&gt;

&lt;p&gt;The UA30 is a complete 30L agricultural drone listed by UNITED UAV. Here, it is a concrete example for developers building application reports and technical teams evaluating a complete aircraft package. The useful question is: &lt;strong&gt;what evidence would let another person reproduce the reported application rate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1ttd20yk3wv7xs2fzb2.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz1ttd20yk3wv7xs2fzb2.webp" alt="UNITED UAV UA30 agricultural drone with its tank, rotor arms and landing gear" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: UNITED UAV sells this aircraft. This article was drafted with AI assistance. Calculations and sample records below are synthetic engineering examples, not UA30 field measurements.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat the aircraft as a system
&lt;/h2&gt;

&lt;p&gt;A complete spraying aircraft combines flight control, positioning, a liquid circuit, atomizers, power, mission planning and operator decisions. Their outputs interact. Changing groundspeed changes area covered per second; changing pump output changes volume delivered; changing altitude or operating conditions can change the usable deposition pattern.&lt;/p&gt;

&lt;p&gt;For procurement, separate the supplied configuration from available options. Request an itemized aircraft, controller, battery, charger and spraying-system list. Establish whether positioning corrections, terrain sensing, spreading equipment and data-export access are included in that particular quotation. A photograph or a broad feature description does not settle those questions.&lt;/p&gt;

&lt;p&gt;Also separate a liquid tank's volume from payload mass. Thirty liters of a formulation does not necessarily weigh thirty kilograms. Density, usable fill level, hardware and the approved mass envelope remain relevant. Software should keep volume and mass in different fields rather than silently converting between them.&lt;/p&gt;

&lt;p&gt;This matters when comparing complete machines: a larger tank may reduce refills, but it cannot establish daily output without flight duration, charging, servicing and field geometry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the units
&lt;/h2&gt;

&lt;p&gt;Let Q be total measured flow in liters per minute, v be groundspeed in meters per second, and w be the validated track spacing in meters. For a straight, steady pass, the nominal application rate is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R [L/ha] = 10,000 × Q / (60 × v × w)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The conversion follows directly from area per second, v × w, and volume per second, Q / 60. Aerial calibration guidance also expresses this relationship using speed in kilometers per hour. The &lt;a href="https://innovationtoimpact.org/wp-content/uploads/2023/07/Calibration-for-Aerial-Application-Equipment_Updated-July-2023.pdf" rel="noopener noreferrer"&gt;I2I aerial-equipment calibration SOP&lt;/a&gt; documents that form; its public-health operating recommendations should not be transferred wholesale to agricultural spraying.&lt;/p&gt;

&lt;p&gt;For an illustrative 6 m/s pass with 6 m track spacing and 4.32 L/min total flow, the result is 20 L/ha. If speed falls to 3 m/s while flow remains unchanged, it becomes 40 L/ha. A route can therefore look geometrically correct while the nominal volume rate doubles.&lt;/p&gt;

&lt;p&gt;These are deliberately chosen example inputs, not an asserted UA30 operating point or a pesticide recommendation. Select the actual target and operating envelope through the applicable product label, qualified agronomic advice and aircraft instructions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate intervals before calculating the mission average
&lt;/h2&gt;

&lt;p&gt;Telemetry rarely arrives at a perfectly uniform rate. Integrate each interval separately: delivered volume equals flow multiplied by elapsed minutes; nominal swept area equals groundspeed multiplied by spacing and elapsed seconds. Then divide total delivered volume by total area.&lt;/p&gt;

&lt;p&gt;Do not average individual rate values without weighting. Five seconds at 6 m/s and five seconds at 3 m/s cover different areas. With the example flow and spacing above, those intervals deliver 0.72 L across 270 square meters, giving approximately 26.67 L/ha. The simple arithmetic average of 20 and 40 is 30, which is wrong for that combined volume and area.&lt;/p&gt;

&lt;p&gt;A small offline checker makes the arithmetic inspectable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isfinite&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;nominal_rate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;liters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;area_m2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;dt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing_m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow_lpm&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;intervals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing_m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flow_lpm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non-finite observation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;speed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing_m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;flow_lpm&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid moving-pass interval&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;liters&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;flow_lpm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dt_s&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
        &lt;span class="n"&gt;area_m2&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;speed_mps&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;spacing_m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;dt_s&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;area_m2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no valid area&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;liters&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;area_m2&lt;/span&gt;

&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nominal_rate&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.32&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-9&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;nominal_rate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;26.6666666667&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function assumes interval-average measurements, straight moving passes and no overlap. It accepts neither raw asynchronous sensor messages nor stationary discharge. Production ingestion must classify those conditions before invoking it; deleting troublesome intervals would create a misleading report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make missing evidence visible
&lt;/h2&gt;

&lt;p&gt;For every interval, retain aircraft identity, mission identity, configuration revision, timestamp basis, elapsed time, spray state, speed, total measured flow, spacing source and quality flags. Distinguish measured flow from a pump command. A requested output is not evidence that the same volume passed through the nozzles.&lt;/p&gt;

&lt;p&gt;Align measurements to a common clock. If position arrives several hundred milliseconds after flow, a speed change can be paired with the wrong discharge interval. Document the alignment method, expected delay and maximum accepted gap. Preserve the original messages so a revised alignment can be evaluated later.&lt;/p&gt;

&lt;p&gt;When data disappear, report unknown coverage and volume separately from accepted intervals. Do not fill a disconnected flow sensor with zero. Zero means an observed absence of flow; missing means the system cannot establish it.&lt;/p&gt;

&lt;p&gt;Before promising integration, ask which exports the delivered UA30 configuration actually provides. Obtain sample files, units, timestamps and field definitions. This article proposes an analysis format; it does not claim that the aircraft exposes a particular public SDK, protocol or telemetry endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate flown area from uniquely treated area
&lt;/h2&gt;

&lt;p&gt;Distance multiplied by spacing is a useful diagnostic denominator, but it counts a repeated pass again. Turns, overlap, revisits and boundary crossings can make nominal swept area larger than the unique area treated inside the field.&lt;/p&gt;

&lt;p&gt;For a spatial report, represent each accepted pass as a footprint in an appropriate projected coordinate system, clip it to the approved field boundary, and calculate the union. Retain overlapping coverage separately rather than discarding it: repeated application can matter even when the unique-area total stays unchanged.&lt;/p&gt;

&lt;p&gt;A geometric buffer is still only a model. The effective width needs deposition assessment for the actual setup and conditions. Positioning accuracy does not prove uniform deposition across that width. Report nominal swept-area rate, unique-area volume rate and any observed deposition results with distinct labels.&lt;/p&gt;

&lt;p&gt;This distinction also prevents an attractive but unsupported conclusion: a low average rate across a large polygon does not prove that every part received an appropriate dose. Underapplication and repeated application can coexist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconcile liquid and the complete work cycle
&lt;/h2&gt;

&lt;p&gt;Compare integrated discharge with an independently measured liquid balance. Account for starting volume, refills, recovered liquid, residual contents and any documented draining or leakage. A disagreement can point to calibration error, an omitted refill, a clock problem or a different measurement boundary.&lt;/p&gt;

&lt;p&gt;Investigate the residual rather than forcing both totals to match. Preserve uncertainty from collection vessels, scales, density measurements and flow calibration. Acceptance tolerances should follow measurement capability and operational requirements, not a convenient number chosen after the demonstration.&lt;/p&gt;

&lt;p&gt;At an illustrative 20 L/ha, a nominal 30L tank gives 1.5 hectares by division alone. Actual dispatch planning must account for usable liquid, required reserve, non-treatment discharge and the energy needed to finish and land. That division says nothing about whether one loaded flight can cover the area.&lt;/p&gt;

&lt;p&gt;For throughput, measure the whole cycle: preparation, takeoff, productive passes, turns, return, refill, battery handling, charging and cooling. Distinguish battery energy from ground charging capacity. Neither charger wattage nor a full tank establishes sustained hectares per hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use discrepancies to choose the next measurement
&lt;/h2&gt;

&lt;p&gt;When reported volume disagrees with the independent liquid balance, start with the liquid circuit and its measurement boundaries. Check priming, residual liquid, refill entries and whether the recorded quantity is measured discharge or a requested setting. Repeating the route faster will not isolate a flow-meter problem.&lt;/p&gt;

&lt;p&gt;When volume agrees but the rate changes between otherwise similar passes, inspect speed alignment, track spacing and spray-state transitions. A lagging speed message can produce a large instantaneous spike while the total volume still looks reasonable. Keep those spikes attached to their timestamps; smoothing them into a reassuring average removes diagnostic evidence.&lt;/p&gt;

&lt;p&gt;When both arithmetic checks agree but deposition is uneven, investigate the physical application pattern. Nozzle condition, liquid properties, aircraft motion, height and wind can matter. A consistent digital report does not establish biological effectiveness or drift control.&lt;/p&gt;

&lt;p&gt;Sensitivity calculations help prioritize measurements. If true flow is 5% above the recorded value while actual effective width is 10% below the assumed width, the true nominal rate is about 16.7% higher than reported: 1.05 divided by 0.90. Neither discrepancy looks dramatic in isolation, yet their effects compound.&lt;/p&gt;

&lt;p&gt;For a complete UA30 evaluation, this creates a practical division of work. The software team checks timing and arithmetic; the equipment team establishes configuration and calibration; the application specialist assesses deposition and permissible operating conditions. Define who supplies each record before the demonstration, so an unexplained result leads to a specific follow-up measurement rather than another sales comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask for a reproducible acceptance record
&lt;/h2&gt;

&lt;p&gt;A useful UA30 demonstration should leave more than a finished-area screenshot. Request the exact configuration, approved operating conditions, calibration record, raw export, refill log and a report showing rejected or unknown intervals. Run the same dataset through an independent calculation and compare the results.&lt;/p&gt;

&lt;p&gt;Begin with controlled calibration and an appropriate nonhazardous test liquid under the aircraft supplier's procedure. Keep physical aircraft protection and operator control independent of this reporting script. The checker evaluates recorded evidence; it neither commands flight nor determines chemical application settings.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://store.uniteduav.com/products/ua30-30l-agricultural-drone" rel="noopener noreferrer"&gt;UA30 product listing&lt;/a&gt; identifies the aircraft discussed here. For a technical evaluation, define the required data exports and acceptance evidence alongside the hardware configuration. A complete spraying system becomes easier to compare when its reported work can be reconstructed from measurements, boundaries and explicit assumptions.&lt;/p&gt;

</description>
      <category>python</category>
      <category>hardware</category>
      <category>datascience</category>
      <category>robotics</category>
    </item>
    <item>
      <title>Static Thrust Is Not Cruise Performance: Building a Fixed-Wing Propulsion Envelope from AT2814 Data</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Mon, 28 Sep 2026 13:58:25 +0000</pubDate>
      <link>https://dev.to/united_uav/static-thrust-is-not-cruise-performance-building-a-fixed-wing-propulsion-envelope-from-at2814-data-28bp</link>
      <guid>https://dev.to/united_uav/static-thrust-is-not-cruise-performance-building-a-fixed-wing-propulsion-envelope-from-at2814-data-28bp</guid>
      <description>&lt;p&gt;A static thrust table is useful evidence, but it is not a cruise-performance map. The distinction matters whenever a motor and propeller are used as the pusher on a fixed-wing UAV or during the wing-borne phase of a VTOL mission. At zero airspeed, the propeller operates at zero advance ratio. In forward flight, its inflow, blade angle of attack, thrust coefficient, torque coefficient, and efficiency all change. A spreadsheet that equates a static gram-of-thrust row with cruise thrust can therefore look precise while answering the wrong question.&lt;/p&gt;

&lt;p&gt;This article develops a data pipeline around the randomly selected &lt;a href="https://store.uniteduav.com/products/at2814-kv1200" rel="noopener noreferrer"&gt;UNITED UAV T-MOTOR AT2814 KV1200 fixed-wing pusher motor&lt;/a&gt;. The &lt;a href="https://store.tmotor.com/product/at2814-long-shaft-fixed-wing-motor.html" rel="noopener noreferrer"&gt;manufacturer's AT2814 page&lt;/a&gt; publishes voltage, current, electrical power, RPM, torque, static thrust, thrust per watt, and terminal motor-surface temperature for several propellers. That is enough to audit electrical and mechanical boundaries and to reject bad combinations. It is not enough to predict cruise without an airspeed-dependent propeller map.&lt;/p&gt;

&lt;p&gt;No UNITED UAV flight test, wind-tunnel test, or calibrated thrust-stand test was performed for this article. Manufacturer rows are identified as source data. The airframe and mission values below are explicit examples used to demonstrate the method, not claims about a production aircraft. Any flight release needs evidence from the exact installed motor, propeller, ESC, battery, cooling path, airframe, firmware, and operating environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial disclosure:&lt;/strong&gt; UNITED UAV Official publishes this article and sells the linked motor. The workflow is intended to remain useful if a different motor or supplier is selected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The aircraft asks for thrust at airspeed, not static grams
&lt;/h2&gt;

&lt;p&gt;In steady, level fixed-wing flight, thrust balances drag. &lt;a href="https://www1.grc.nasa.gov/beginners-guide-to-aeronautics/drag-equation/" rel="noopener noreferrer"&gt;NASA's drag equation&lt;/a&gt; expresses drag as dynamic pressure multiplied by reference area and drag coefficient. A preliminary aircraft model often expands the coefficient into a zero-lift term and a lift-dependent term:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CD = CD0 + k × CL²&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With lift approximately equal to weight in level flight, the corresponding drag estimate is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;D = q × S × CD0 + k × W² / (q × S)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;where &lt;code&gt;q = 0.5 × rho × V²&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt; is the declared wing reference area, &lt;code&gt;W&lt;/code&gt; is weight, &lt;code&gt;rho&lt;/code&gt; is air density, and &lt;code&gt;V&lt;/code&gt; is true airspeed. The model is only as credible as its coefficients and reference conventions. &lt;code&gt;CD0&lt;/code&gt;, span efficiency, wing area, mass, and density must carry provenance. Wind-tunnel or flight-identification data should replace illustrative assumptions as soon as they exist.&lt;/p&gt;

&lt;p&gt;This framing immediately exposes a common category error. A multirotor hover calculation begins with weight divided by lifting rotors. A fixed-wing cruise calculation begins with aircraft drag at a flight condition. The pusher does not normally support the aircraft's weight directly; the wing does. During climb, the propulsion system must provide both the power associated with drag and the rate of increase of potential energy. During acceleration, it must additionally supply excess thrust. During VTOL transition, neither a pure hover model nor a steady fixed-wing model is sufficient.&lt;/p&gt;

&lt;p&gt;For an illustrative 1.35 kg airframe, assume a 0.34 m² wing, &lt;code&gt;CD0 = 0.035&lt;/code&gt;, aspect ratio 7.5, span efficiency 0.8, and sea-level density 1.225 kg/m³. These are teaching inputs, not measured properties. At 18 m/s, the simple polar produces about 2.50 N of drag. The mechanical power delivered to useful propulsive work in level flight would be at least &lt;code&gt;D × V&lt;/code&gt;, approximately 45 W, before accounting for propeller efficiency, motor/ESC loss, installation effects, and control activity.&lt;/p&gt;

&lt;p&gt;During a 3 m/s climb, the potential-energy rate alone is approximately &lt;code&gt;W × climb_rate&lt;/code&gt;, or about 40 W. The shaft-power requirement is therefore not obtained by looking for 1.35 kg in a static thrust table. It comes from the drag polar, climb requirement, airspeed, propulsive efficiency, drivetrain efficiency, and margins. Each input must be tied to an operating condition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advance ratio is the missing coordinate
&lt;/h2&gt;

&lt;p&gt;Propeller data are commonly organized by advance ratio:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;J = V / (n × D)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;where &lt;code&gt;V&lt;/code&gt; is forward speed, &lt;code&gt;n&lt;/code&gt; is revolutions per second, and &lt;code&gt;D&lt;/code&gt; is propeller diameter. A static bench point has &lt;code&gt;V = 0&lt;/code&gt;, so &lt;code&gt;J = 0&lt;/code&gt;. A fixed-wing cruise point does not.&lt;/p&gt;

&lt;p&gt;Consider the manufacturer's published full-command rows for the AT2814 KV1200. With an APC 9×6 propeller, the table reports 12,788 RPM at 14.46 V. At 18 m/s, that RPM and diameter correspond to an advance ratio of roughly 0.37. The APC 10×5.5 row reports 12,029 RPM at 14.37 V, corresponding to roughly 0.35 at the same airspeed. Those values are not small perturbations around a static point; they identify a different propeller operating condition.&lt;/p&gt;

&lt;p&gt;Without thrust and power coefficients across advance ratio—or a validated wind-tunnel, dynamometer, or flight-derived map—the static table cannot tell us cruise thrust, cruise torque, or propulsive efficiency. Scaling static thrust by voltage, RPM squared, or throttle percentage does not restore the missing inflow physics. A responsible model should represent that state as &lt;code&gt;unknown&lt;/code&gt;, not fill it with a convenient extrapolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the published boundary data
&lt;/h2&gt;

&lt;p&gt;The manufacturer's current specification for the long-shaft KV1200 identifies a 108 g motor including cable, 26 mΩ internal resistance, 12N14P configuration, 5 mm input and output shaft diameters, 3–4S LiPo range, 1.8 A idle current at 10 V, 55 A peak current for 180 seconds, and 800 W maximum power for 180 seconds. These are motor-level, duration-qualified figures. They are not continuous ratings for the ESC, battery, connector, or installed aircraft.&lt;/p&gt;

&lt;p&gt;Representative KV1200 source rows are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Supply / propeller&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Voltage&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Input power&lt;/th&gt;
&lt;th&gt;RPM&lt;/th&gt;
&lt;th&gt;Torque&lt;/th&gt;
&lt;th&gt;Static thrust&lt;/th&gt;
&lt;th&gt;Listed surface temperature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 9×6&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;14.78 V&lt;/td&gt;
&lt;td&gt;30.26 A&lt;/td&gt;
&lt;td&gt;447.22 W&lt;/td&gt;
&lt;td&gt;11,085&lt;/td&gt;
&lt;td&gt;0.289 N·m&lt;/td&gt;
&lt;td&gt;1,611 g&lt;/td&gt;
&lt;td&gt;85 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 9×6&lt;/td&gt;
&lt;td&gt;90%&lt;/td&gt;
&lt;td&gt;14.54 V&lt;/td&gt;
&lt;td&gt;45.13 A&lt;/td&gt;
&lt;td&gt;656.04 W&lt;/td&gt;
&lt;td&gt;12,479&lt;/td&gt;
&lt;td&gt;0.379 N·m&lt;/td&gt;
&lt;td&gt;2,044 g&lt;/td&gt;
&lt;td&gt;85 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 9×6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14.46 V&lt;/td&gt;
&lt;td&gt;49.57 A&lt;/td&gt;
&lt;td&gt;716.86 W&lt;/td&gt;
&lt;td&gt;12,788&lt;/td&gt;
&lt;td&gt;0.402 N·m&lt;/td&gt;
&lt;td&gt;2,152 g&lt;/td&gt;
&lt;td&gt;85 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 10×5.5&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;14.72 V&lt;/td&gt;
&lt;td&gt;33.92 A&lt;/td&gt;
&lt;td&gt;499.21 W&lt;/td&gt;
&lt;td&gt;10,615&lt;/td&gt;
&lt;td&gt;0.324 N·m&lt;/td&gt;
&lt;td&gt;1,982 g&lt;/td&gt;
&lt;td&gt;97 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 10×5.5&lt;/td&gt;
&lt;td&gt;80%&lt;/td&gt;
&lt;td&gt;14.64 V&lt;/td&gt;
&lt;td&gt;38.94 A&lt;/td&gt;
&lt;td&gt;569.96 W&lt;/td&gt;
&lt;td&gt;11,052&lt;/td&gt;
&lt;td&gt;0.355 N·m&lt;/td&gt;
&lt;td&gt;2,169 g&lt;/td&gt;
&lt;td&gt;97 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4S / APC 10×5.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;14.37 V&lt;/td&gt;
&lt;td&gt;54.64 A&lt;/td&gt;
&lt;td&gt;785.36 W&lt;/td&gt;
&lt;td&gt;12,029&lt;/td&gt;
&lt;td&gt;0.436 N·m&lt;/td&gt;
&lt;td&gt;2,616 g&lt;/td&gt;
&lt;td&gt;97 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3S / APC 10×5.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;10.90 V&lt;/td&gt;
&lt;td&gt;35.83 A&lt;/td&gt;
&lt;td&gt;390.52 W&lt;/td&gt;
&lt;td&gt;10,077&lt;/td&gt;
&lt;td&gt;0.285 N·m&lt;/td&gt;
&lt;td&gt;1,767 g&lt;/td&gt;
&lt;td&gt;66 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3S / APC 11×5.5&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;10.80 V&lt;/td&gt;
&lt;td&gt;41.64 A&lt;/td&gt;
&lt;td&gt;449.82 W&lt;/td&gt;
&lt;td&gt;9,503&lt;/td&gt;
&lt;td&gt;0.332 N·m&lt;/td&gt;
&lt;td&gt;2,044 g&lt;/td&gt;
&lt;td&gt;82 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The source states that the temperature is motor surface temperature after three minutes at 100% command. It does not assign that temperature independently to every partial-command row. Store it as a terminal test-condition observation for the configuration, not as a temperature measured at 75% or 90%. Sensor placement, emissivity, airflow, starting temperature, and ambient temperature are not fully specified in the displayed rows, so the values cannot be converted into a universal thermal model.&lt;/p&gt;

&lt;p&gt;Basic arithmetic should be automated. In the 4S 10×5.5 full-command row, 14.37 V multiplied by 54.64 A is about 785.4 W, consistent with the listed 785.36 W after rounding. The row is only 0.36 A below the motor's published 55 A / 180-second current figure and only 14.64 W below the published 800 W / 180-second power figure. That is boundary evidence, not usable design margin. Battery sag, sensor error, propeller tolerance, a denser atmosphere, cooling differences, or an ESC timing change can move the installed point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare configurations at a declared static condition
&lt;/h2&gt;

&lt;p&gt;Static data are still valuable when used for a static requirement: launch checks, restrained ground tests, low-speed transition screening, or source-data quality control. Comparisons must be made at the same thrust and within one measured curve.&lt;/p&gt;

&lt;p&gt;For example, the 4S 9×6 row reports 2,044 g at 90% and 656.04 W. The 4S 10×5.5 curve brackets the same static thrust between 1,982 g at 499.21 W and 2,169 g at 569.96 W. Linear interpolation within that narrow source interval gives roughly 522.7 W at 2,044 g. Under those particular static bench conditions, the larger propeller needs about 20% less listed input power at that target.&lt;/p&gt;

&lt;p&gt;That result is useful but deliberately narrow. It does not prove lower cruise energy. It does not resolve propeller clearance, noise, vibration, control response, launch behavior, forward-flight efficiency, or the much hotter published terminal surface-temperature value for the 10×5.5 configuration. It also does not turn linear interpolation into a physical law. The interpolation should retain its two bracketing rows and refuse targets outside the measured interval.&lt;/p&gt;

&lt;p&gt;The right conclusion is “this combination deserves the next test at the defined operating condition,” not “this propeller is 20% more efficient in flight.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a data contract that keeps unlike evidence separate
&lt;/h2&gt;

&lt;p&gt;Every observation should identify both its configuration and evidence class. A minimum propulsion record includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;motor model and winding;&lt;/li&gt;
&lt;li&gt;propeller manufacturer, diameter, pitch, material, and revision;&lt;/li&gt;
&lt;li&gt;ESC model, firmware, timing, and switching settings;&lt;/li&gt;
&lt;li&gt;battery chemistry, cell count, state of charge, temperature, and loaded voltage;&lt;/li&gt;
&lt;li&gt;command, RPM, voltage, current, electrical measurement boundary, torque, and thrust;&lt;/li&gt;
&lt;li&gt;airspeed, air density, angle of attack, and installation state;&lt;/li&gt;
&lt;li&gt;sensor IDs, calibration dates, sample windows, and uncertainty;&lt;/li&gt;
&lt;li&gt;source type such as &lt;code&gt;manufacturer_static&lt;/code&gt;, &lt;code&gt;local_static&lt;/code&gt;, &lt;code&gt;wind_tunnel&lt;/code&gt;, or &lt;code&gt;flight_identified&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;raw-data URI, checksum, test procedure revision, and reviewer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not merge rows simply because the column names match. A manufacturer static row and a flight-identified cruise row have different authority, conditions, and uncertainty. A value copied from a page is not a measurement made by your team. An absent airspeed must be &lt;code&gt;null&lt;/code&gt; with a reason such as &lt;code&gt;static_test&lt;/code&gt;, never silently encoded as zero unless the source explicitly establishes zero inflow.&lt;/p&gt;

&lt;p&gt;A configuration fingerprint can be generated from normalized identity fields. If the motor winding, propeller revision, ESC firmware, or measurement boundary changes, the fingerprint must change. That prevents a dashboard from averaging incompatible tests into a reassuring but meaningless trend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit the source rows and mission model in code
&lt;/h2&gt;

&lt;p&gt;The following dependency-free Python example performs three bounded jobs. It checks electrical power consistency, derives shaft power from published torque and RPM, and interpolates static power only within a matching curve. Separately, it evaluates an illustrative drag polar and reports advance ratio. It never converts static thrust into cruise thrust.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;diameter_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;torque_nm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diameter_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;torque_nm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid static point&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non-positive value or command above 100%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;recomputed_input_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;
    &lt;span class="n"&gt;power_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recomputed_input_w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;power_error_fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;power_error&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;power_error_fraction&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.015&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input power disagrees with voltage times current&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;shaft_power_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;torque_nm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;shaft_power_w&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1.02&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shaft power exceeds plausible electrical input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;diameter_m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diameter_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0254&lt;/span&gt;
    &lt;span class="n"&gt;tip_speed_mps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;diameter_m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recomputed_input_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;recomputed_input_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shaft_power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shaft_power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drive_efficiency&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shaft_power_w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static_tip_mach_at_340_3_mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tip_speed_mps&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;340.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolate_static_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;target_thrust_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;target_thrust_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target thrust must be positive and finite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at least two source points are required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;audit_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;identities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diameter_in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identities&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mixed propeller curves cannot be interpolated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;bracket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
         &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;target_thrust_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bracket&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target would require extrapolation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bracket&lt;/span&gt;
    &lt;span class="n"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_thrust_g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_static_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target_thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_power_w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bracket_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interpolated_manufacturer_static&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;drag_polar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mass_kg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;wing_area_m2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cd0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;aspect_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span_efficiency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mass_kg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wing_area_m2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;cd0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aspect_ratio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;span_efficiency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drag-polar inputs must be positive and finite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span_efficiency&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;span efficiency must not exceed 1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;weight_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mass_kg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;9.80665&lt;/span&gt;
    &lt;span class="n"&gt;dynamic_pressure_pa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;rho&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;airspeed_mps&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;aspect_ratio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;span_efficiency&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parasite_drag_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamic_pressure_pa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;wing_area_m2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cd0&lt;/span&gt;
    &lt;span class="n"&gt;induced_drag_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;weight_n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dynamic_pressure_pa&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;wing_area_m2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;drag_n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parasite_drag_n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;induced_drag_n&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;drag_n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drag_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parasite_drag_n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;parasite_drag_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;induced_drag_n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;induced_drag_n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;useful_level_flight_power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;drag_n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;illustrative_airframe_model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;advance_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diameter_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diameter_in&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;rpm&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;diameter_in&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid advance-ratio inputs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;revolutions_per_second&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rpm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="n"&gt;diameter_m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;diameter_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.0254&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;airspeed_mps&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;revolutions_per_second&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;diameter_m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;prop_10x55&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APC 10x5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;33.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mf"&gt;499.21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10615&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.324&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1982&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APC 10x5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;38.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mf"&gt;569.96&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11052&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.355&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2169&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;StaticPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;APC 10x5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;14.37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;54.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="mf"&gt;785.36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12029&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.436&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2616&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;audit_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prop_10x55&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;interpolate_static_power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prop_10x55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_thrust_g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2044&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;drag_polar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mass_kg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;airspeed_mps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rho&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.225&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;wing_area_m2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cd0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.035&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;aspect_ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;7.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;span_efficiency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;advance_ratio_at_18_mps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;advance_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12029&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;static_table_advance_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The program should report approximately 549 W of shaft power and 70% electrical-to-shaft efficiency for the full-command 4S 10×5.5 row, subject to the published torque and electrical measurement boundaries. It should interpolate approximately 523 W and 35.6 A at 2,044 g static thrust. The illustrative airframe model should return about 2.50 N drag and 45 W of useful level-flight power at 18 m/s. The reported advance ratio should be about 0.35, while every static table row remains at zero.&lt;/p&gt;

&lt;p&gt;Those outputs are intentionally not merged into a cruise-current prediction. Doing so would require a propeller map as a function of advance ratio, Reynolds number, and possibly blade angle and installation condition. The code preserves the gap instead of laundering an assumption into a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convert the model into explicit gates
&lt;/h2&gt;

&lt;p&gt;A useful screening service returns more than a ranked list. It returns failed constraints and missing evidence. For this class of propulsion decision, sensible gates include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identity gate.&lt;/strong&gt; Exact motor winding, propeller, ESC hardware and firmware, supply, and source revision are known.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arithmetic gate.&lt;/strong&gt; Voltage-current power, torque-RPM shaft power, units, monotonicity, and duplicate rows pass validation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static-envelope gate.&lt;/strong&gt; Takeoff or low-speed static requirements fall inside measured brackets; no extrapolation is used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Electrical gate.&lt;/strong&gt; Continuous and transient current, power, connector, cable, distribution, and battery limits have duration-aware margin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic-propeller gate.&lt;/strong&gt; Required cruise and climb advance ratios are covered by trusted propeller coefficients or a representative test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Thermal gate.&lt;/strong&gt; The installed system completes the intended duty cycle at the hot operating condition without exceeding approved limits or retaining an unacceptable positive temperature slope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mechanical gate.&lt;/strong&gt; Shaft, fasteners, propeller retention, balance, clearance, mount stiffness, and vibration meet the airframe acceptance criteria.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control gate.&lt;/strong&gt; Command headroom, transition behavior, RPM response, governor or ESC behavior, and fault handling are verified with the flight-control configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the dynamic-propeller map is missing, the correct state is &lt;code&gt;requires_dynamic_test&lt;/code&gt;. The system may still be suitable, but the evidence does not yet support a cruise claim. This distinction is important for automated selection tools: “no qualifying candidate” and “insufficient data” are different outcomes and should lead to different engineering work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design the dynamic test around the missing coordinate
&lt;/h2&gt;

&lt;p&gt;The next test should add controlled airspeed, not repeat more static points. A wind-tunnel or propeller dynamometer setup should measure thrust and torque across the intended combinations of RPM and inflow speed. Convert observations to nondimensional thrust and power coefficients, record advance ratio, and preserve air density, temperature, pressure, propeller geometry, and uncertainty.&lt;/p&gt;

&lt;p&gt;For an installed pusher, test installation effects. The fuselage, wing, tail, motor mount, cooling inlet, and propeller clearance can change inflow and efficiency. A tractor-propeller map is not automatically transferable to a pusher location. A VTOL transition adds changing angle of attack, possible lift-rotor wake interaction, and rapidly changing demand. The test matrix should cover the actual operating corridor rather than a single nominal cruise point.&lt;/p&gt;

&lt;p&gt;Synchronize electrical, mechanical, and aerodynamic measurements. Voltage and current averaged over one window cannot be paired with thrust and torque from another during a ramp. Record raw timestamps and the exact aggregation method. Repeat points in randomized order or revisit baselines to expose heating, battery depletion, and sensor drift.&lt;/p&gt;

&lt;p&gt;Propeller testing is hazardous. Use a suitably engineered enclosure, guarded and restrained hardware, an exclusion zone, remote observation, and an independent emergency power disconnect. Analysis code must remain outside the flight-critical or emergency-control path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat limits as time-dependent
&lt;/h2&gt;

&lt;p&gt;The manufacturer's 55 A and 800 W values are labeled for 180 seconds. A mission model must therefore integrate duration as well as amplitude. Store each segment with its start condition, expected current and power distribution, duration, and recovery interval. A three-second launch burst, a sixty-second climb, and twenty minutes of cruise are not interchangeable merely because their peak current is similar.&lt;/p&gt;

&lt;p&gt;Thermal state carries between segments. The motor entering a go-around after a long climb is not at the same initial temperature as a cold bench unit. ESC and battery temperature also alter loss and protection behavior. A useful model propagates temperature or, before such a model is validated, requires representative sequence testing.&lt;/p&gt;

&lt;p&gt;Do not infer a continuous limit from the fact that one source row is below a 180-second maximum. The 4S 10×5.5 full-command row nearly consumes the published transient current and power limits and carries a listed 97 °C terminal surface temperature under the source procedure. It is a boundary point for validation, not a cruise target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Close the loop with flight telemetry
&lt;/h2&gt;

&lt;p&gt;After ground and dynamic qualification, flight data should be traceable to the same configuration fingerprint. Record true or calibrated airspeed where available, RPM, motor command, pack and ESC voltage, current, motor and ESC temperature, altitude, ambient estimates, battery state, aircraft mass configuration, and flight mode. Preserve controller saturation and transition-state flags; otherwise high current caused by a control problem can be mistaken for normal propulsion demand.&lt;/p&gt;

&lt;p&gt;Compare measured power against the mission model by segment and by advance-ratio region. A persistent residual may indicate an inaccurate drag polar, propeller-map error, airspeed bias, installation loss, mass mismatch, or drivetrain degradation. Do not immediately tune a correction factor to erase it. Diagnose which boundary or assumption is wrong and retain the pre-correction evidence.&lt;/p&gt;

&lt;p&gt;Operational monitoring should compare like configurations and conditions. Useful signals include increasing current at matched airspeed and mass, declining RPM at matched voltage and command, growing vibration, reduced climb excess power, rising temperature slope, and repeated ESC protection events. Missing telemetry must be flagged as missing; zero is a real value.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AT2814 data can and cannot decide
&lt;/h2&gt;

&lt;p&gt;The published AT2814 KV1200 table can support source-data validation, static combination screening, motor/ESC/battery boundary checks, approximate shaft-power reconciliation, tip-speed checks, and definition of the next test. It reveals that the 4S 10×5.5 full-command point is very close to the motor's published 180-second current and power figures. It also shows a substantial static input-power difference between 9×6 and 10×5.5 combinations near 2,044 g of thrust.&lt;/p&gt;

&lt;p&gt;It cannot establish cruise thrust, cruise efficiency, range, endurance, climb rate, hot-day continuous capability, pusher-installation loss, transition behavior, or aircraft suitability by itself. Those outputs require airframe aerodynamics, advance-ratio-dependent propeller data, installed electrical and thermal evidence, and flight validation.&lt;/p&gt;

&lt;p&gt;That boundary is the core engineering result. Professional propulsion software should make valid deductions easy, invalid extrapolations impossible, and missing evidence visible. A static table becomes useful not when it is treated as a complete answer, but when it is integrated into a mission-specific evidence chain with explicit refusal rules.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>python</category>
      <category>datascience</category>
      <category>performance</category>
    </item>
    <item>
      <title>From Mission Requirements to a UAV Motor Operating Point: An MN4010 Sizing Workflow</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Sun, 20 Sep 2026 15:18:24 +0000</pubDate>
      <link>https://dev.to/united_uav/from-mission-requirements-to-a-uav-motor-operating-point-an-mn4010-sizing-workflow-3pjn</link>
      <guid>https://dev.to/united_uav/from-mission-requirements-to-a-uav-motor-operating-point-an-mn4010-sizing-workflow-3pjn</guid>
      <description>&lt;p&gt;A motor is not selected when its maximum-thrust number exceeds the aircraft weight. It is selected only after the motor, propeller, voltage, ESC, battery, airframe, and control reserve have been evaluated at the operating points the mission will actually use.&lt;/p&gt;

&lt;p&gt;This article builds that evaluation around the randomly selected &lt;a href="https://store.uniteduav.com/products/mn4010-kv370" rel="noopener noreferrer"&gt;UNITED UAV T-MOTOR MN4010 KV370 Navigator motor&lt;/a&gt;. The useful part of this example is not the product name. It is the unusually complete set of &lt;a href="https://store.tmotor.com/product/mn4010-kv370-motor-navigator-type.html" rel="noopener noreferrer"&gt;manufacturer-published MN4010 test rows&lt;/a&gt;: several propellers, both 14.8 V and 22.2 V supplies, and current, power, thrust, RPM, and temperature at multiple command points. That is enough data to show how a mission requirement can become a traceable propulsion decision instead of a spreadsheet guess.&lt;/p&gt;

&lt;p&gt;No UNITED UAV flight test or calibrated thrust-stand test was performed for this article. All MN4010 measurements cited below are published manufacturer reference data. The aircraft and mission are an explicit engineering example, not a claim about a production vehicle. Before flight, reproduce the relevant points on the installed propulsion system and use limits approved for that aircraft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial disclosure:&lt;/strong&gt; UNITED UAV Official publishes this article and sells the linked motor. The workflow is vendor-neutral and is designed to expose, rather than hide, the assumptions behind a selection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Begin with a load-case contract
&lt;/h2&gt;

&lt;p&gt;Suppose a team is designing a six-rotor mapping aircraft with a maximum takeoff mass of 4.5 kg. Its nominal static hover allocation is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;4500 g / 6 = 750 g per rotor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That value is not yet a motor requirement. The installed aircraft may have unequal arm loading, non-ideal inflow near the fuselage, propeller-to-propeller variation, battery voltage sag, and lower air density than the bench environment. For a first screening calculation, the team assigns a 13% installed-condition factor:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;750 g × 1.13 = 847.5 g per rotor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We will round the screening hover target to 850 g. The factor is an example input, not a universal safety constant. A real project should derive it from mass-properties analysis, expected density altitude, installation measurements, manufacturing tolerance, and the applicable safety process.&lt;/p&gt;

&lt;p&gt;The same contract requires at least 1.6 times the corrected hover target at the published high-command point:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;850 g × 1.6 = 1360 g per rotor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This second constraint matters. A combination can be extremely efficient at hover and still leave too little authority for climb, gust rejection, load redistribution, or control saturation. Conversely, a combination with impressive maximum thrust can waste energy during the long hover segment. A defensible choice has to pass both the normal operating point and the reserve condition.&lt;/p&gt;

&lt;p&gt;Write the contract as data before inspecting candidates. At minimum, include aircraft mass, lifting-rotor count, corrected hover target, required thrust ratio, maximum propeller diameter, allowed supply-voltage range, mission duration, ambient envelope, maximum permitted current at each electrical boundary, and the policy for interpolation. If a value is unknown, mark it unknown. Quietly replacing an unknown with an optimistic default is how a screening spreadsheet becomes an unsafe specification.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preserve the source rows, not just a marketing maximum
&lt;/h2&gt;

&lt;p&gt;The manufacturer's current page identifies the KV370 version as a 4–8S motor with a listed 137 g mass including its cable, 98 mΩ internal resistance, 0.8 A idle current at 10 V, and 450 W / 20 A figures labeled for 180 seconds. Its test table includes these representative KV370 observations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Supply&lt;/th&gt;
&lt;th&gt;Propeller&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Power&lt;/th&gt;
&lt;th&gt;Thrust&lt;/th&gt;
&lt;th&gt;RPM&lt;/th&gt;
&lt;th&gt;Listed temperature&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;14×4.8 CF&lt;/td&gt;
&lt;td&gt;85%&lt;/td&gt;
&lt;td&gt;5.4 A&lt;/td&gt;
&lt;td&gt;79.92 W&lt;/td&gt;
&lt;td&gt;810 g&lt;/td&gt;
&lt;td&gt;4,400&lt;/td&gt;
&lt;td&gt;44 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;14×4.8 CF&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;6.5 A&lt;/td&gt;
&lt;td&gt;96.20 W&lt;/td&gt;
&lt;td&gt;920 g&lt;/td&gt;
&lt;td&gt;4,700&lt;/td&gt;
&lt;td&gt;44 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;15×5 CF&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;5.1 A&lt;/td&gt;
&lt;td&gt;75.48 W&lt;/td&gt;
&lt;td&gt;820 g&lt;/td&gt;
&lt;td&gt;3,800&lt;/td&gt;
&lt;td&gt;44 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;15×5 CF&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;8.2 A&lt;/td&gt;
&lt;td&gt;121.36 W&lt;/td&gt;
&lt;td&gt;1,160 g&lt;/td&gt;
&lt;td&gt;4,450&lt;/td&gt;
&lt;td&gt;44 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;16×5.4 CF&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;4.3 A&lt;/td&gt;
&lt;td&gt;63.64 W&lt;/td&gt;
&lt;td&gt;770 g&lt;/td&gt;
&lt;td&gt;3,300&lt;/td&gt;
&lt;td&gt;45 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;16×5.4 CF&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;6.1 A&lt;/td&gt;
&lt;td&gt;90.28 W&lt;/td&gt;
&lt;td&gt;970 g&lt;/td&gt;
&lt;td&gt;3,650&lt;/td&gt;
&lt;td&gt;45 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.8 V&lt;/td&gt;
&lt;td&gt;16×5.4 CF&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;9.6 A&lt;/td&gt;
&lt;td&gt;142.08 W&lt;/td&gt;
&lt;td&gt;1,380 g&lt;/td&gt;
&lt;td&gt;4,300&lt;/td&gt;
&lt;td&gt;45 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22.2 V&lt;/td&gt;
&lt;td&gt;12×4 CF&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;5.0 A&lt;/td&gt;
&lt;td&gt;111.00 W&lt;/td&gt;
&lt;td&gt;860 g&lt;/td&gt;
&lt;td&gt;6,200&lt;/td&gt;
&lt;td&gt;40 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22.2 V&lt;/td&gt;
&lt;td&gt;13×4.4 CF&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;4.3 A&lt;/td&gt;
&lt;td&gt;95.46 W&lt;/td&gt;
&lt;td&gt;810 g&lt;/td&gt;
&lt;td&gt;5,300&lt;/td&gt;
&lt;td&gt;47 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22.2 V&lt;/td&gt;
&lt;td&gt;14×4.8 CF&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;3.5 A&lt;/td&gt;
&lt;td&gt;77.70 W&lt;/td&gt;
&lt;td&gt;780 g&lt;/td&gt;
&lt;td&gt;4,400&lt;/td&gt;
&lt;td&gt;51 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22.2 V&lt;/td&gt;
&lt;td&gt;14×4.8 CF&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;td&gt;5.7 A&lt;/td&gt;
&lt;td&gt;126.54 W&lt;/td&gt;
&lt;td&gt;1,060 g&lt;/td&gt;
&lt;td&gt;5,100&lt;/td&gt;
&lt;td&gt;51 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22.2 V&lt;/td&gt;
&lt;td&gt;14×4.8 CF&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;11.7 A&lt;/td&gt;
&lt;td&gt;259.74 W&lt;/td&gt;
&lt;td&gt;1,830 g&lt;/td&gt;
&lt;td&gt;6,600&lt;/td&gt;
&lt;td&gt;51 °C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The page notes that the temperature field is motor surface temperature after a ten-minute run at 100% command. That note is part of the data. It does not establish winding temperature, hot-day continuous capability, or the temperature of a motor partly shielded by an airframe. Likewise, the 180-second current and power labels are duration-limited component figures, not permission to operate the whole installed system at those values.&lt;/p&gt;

&lt;p&gt;Keep the original source URL, retrieval date, model and winding, voltage, propeller identity, and every published column. Do not copy only thrust and current into a design sheet. RPM can reveal a transcription error. Voltage is required to reconcile power. Test notes define what the temperature means. Provenance lets a later reviewer distinguish manufacturer literature from local acceptance evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not treat KV as a complete propulsion model
&lt;/h2&gt;

&lt;p&gt;KV is approximately the no-load speed constant expressed in RPM per volt. Multiplying 370 KV by 22.2 V may be useful as a rough no-load sanity check, but it does not predict loaded RPM, thrust, torque, current, or efficiency. The loaded operating point emerges from the motor, ESC timing, supply impedance, propeller torque curve, and airflow together.&lt;/p&gt;

&lt;p&gt;The table demonstrates this directly. At 22.2 V with the 14×4.8 propeller, the published 100% point is 6,600 RPM, well below the simplistic no-load product. The difference is not a defect; it is evidence that the propeller is loading the motor. A design process that estimates thrust from KV and voltage alone has discarded the dominant aerodynamic interaction.&lt;/p&gt;

&lt;p&gt;Command percentage is also not a portable physical unit. It is a point on one ESC/controller mapping under one test setup. The required quantity is thrust. When comparing propellers, interpolate against thrust inside each measured curve. Never assume that 70% on one propeller is equivalent to 70% on another, and never extrapolate outside the measured range merely because a polynomial can produce a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpolate with explicit refusal rules
&lt;/h2&gt;

&lt;p&gt;For the 850 g screening target, the 14.8 V 16×5.4 curve brackets the requirement between 770 g and 970 g. Linear interpolation places the target 40% of the way through that interval. That produces approximately 5.02 A and 74.30 W. The same calculation for the 22.2 V 14×4.8 curve, bracketed by 780 g and 1,060 g, produces approximately 4.05 A and 89.91 W.&lt;/p&gt;

&lt;p&gt;Linear interpolation is not a claim that the underlying motor-propeller physics are linear. It is a local screening method between adjacent measurements. Its result should retain the two bracket rows and an &lt;code&gt;interpolated&lt;/code&gt; flag. If the target is outside the source curve, if voltage differs, if the propeller identity changes, or if the bracket is too wide for the project's accuracy requirement, the program must refuse the calculation.&lt;/p&gt;

&lt;p&gt;The 14.8 V 15×5 curve also brackets 850 g and would use about 79.48 W by the same method. However, its published maximum of 1,160 g fails the 1,360 g reserve requirement. The 14.8 V 14×4.8 curve fails that reserve even more clearly. Ranking only by interpolated hover power without enforcing reserve would therefore return a result that violates the aircraft contract.&lt;/p&gt;

&lt;p&gt;The 14.8 V 16×5.4 combination reaches a published 1,380 g, only 20 g above the illustrative reserve threshold. It technically passes the simple screen but leaves about 1.5% margin between the required and published values. That is too narrow to absorb the uncertainties we explicitly said existed. A production rule might require the source maximum to exceed the reserve target by an additional measured uncertainty allowance. Under such a rule, this candidate would be rejected or moved to a mandatory local test rather than silently accepted.&lt;/p&gt;

&lt;p&gt;The 22.2 V 14×4.8 combination reaches a published 1,830 g and therefore offers more screening headroom, but it consumes about 21% more electrical power at the interpolated 850 g point than the 14.8 V 16×5.4 combination. This is the engineering trade: hover energy, command headroom, propeller clearance, acoustic behavior, current, voltage, control response, and thermal state are coupled. There is no single “best motor efficiency” number that resolves all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the screening algorithm auditable
&lt;/h2&gt;

&lt;p&gt;The following dependency-free Python program implements the narrow decision described above. It validates the source rows, groups only identical motor/voltage/propeller configurations, brackets a target without extrapolation, interpolates current and power, and then applies a separate maximum-thrust reserve constraint. It deliberately does not invent density corrections or thermal limits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isfinite&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;
    &lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid source point&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rpm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;measurements must be positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command must be in (0, 100]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;relative_power_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;relative_power_error&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.015&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published power is inconsistent with V × A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;interpolate_at_thrust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target thrust must be positive and finite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;at least two source points are required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;voltages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;propellers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;voltages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;propellers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mixed configurations cannot be interpolated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thrust points must be unique and increasing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;bracket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bracket&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target would require extrapolation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bracket&lt;/span&gt;
    &lt;span class="n"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;fraction&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voltage_v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;voltage_v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;propeller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command_pct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bracket_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published_max_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;thrust_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interpolated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;hover_target_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="n"&gt;minimum_thrust_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;interpolate_at_thrust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover_target_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reserve_target_g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hover_target_g&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;minimum_thrust_ratio&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reserve_target_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reserve_target_g&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;passes_published_reserve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published_max_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;reserve_target_g&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reserve_margin_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published_max_thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;reserve_target_g&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="n"&gt;mn4010_4s_16x54&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;14.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 16x5.4 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;63.64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;770&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;14.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 16x5.4 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;90.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;970&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3650&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;14.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 16x5.4 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;9.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;142.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1380&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4300&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;mn4010_6s_14x48&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;22.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 14x4.8 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;77.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4400&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;22.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 14x4.8 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;5.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;126.54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;22.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;T-MOTOR 14x4.8 CF&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;11.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;259.74&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1830&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;curve&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mn4010_4s_16x54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mn4010_6s_14x48&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hover_target_g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;850&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minimum_thrust_ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should show roughly 5.02 A, 74.30 W, and 20 g of published reserve margin for the 4S 16×5.4 curve; and roughly 4.05 A, 89.91 W, and 470 g for the 6S 14×4.8 curve. The lower current on 6S does not mean lower battery stress by itself: pack construction, capacity, C-rate, wiring, connector loss, state of charge, and total six-motor current all matter. Power and heat must be evaluated at the correct boundary.&lt;/p&gt;

&lt;p&gt;The function also performs a 1.5% consistency check between the published power and &lt;code&gt;voltage × current&lt;/code&gt;. Such checks catch unit and transcription mistakes, but small differences can result from rounded display values or measurements averaged over different windows. Store the source value and the recomputed value; do not overwrite the source to make the table look cleaner.&lt;/p&gt;

&lt;p&gt;For production use, add a schema version, source identifier, retrieval timestamp, test report number, unit metadata, uncertainty fields, calibration state, and immutable raw-data hash. Keep rejected rows with their rejection reasons. A system that silently drops malformed observations can produce a polished but unauditable ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate power over the mission, not over one row
&lt;/h2&gt;

&lt;p&gt;Hover is often the longest segment, but it is not the whole mission. Create a timeline of takeoff, climb, transit, mapping, loiter, descent, and reserve. Each segment should specify duration and per-rotor thrust distribution, not merely a throttle percentage. Interpolate power only within a validated curve and sum energy across the timeline:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mission energy = Σ(power at segment operating point × segment duration)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For six rotors at the illustrative 850 g point, the interpolated motor/ESC input implied by the manufacturer rows is about 446 W for the 4S 16×5.4 option or 539 W for the 6S 14×4.8 option. Those numbers exclude avionics, payload, conversion losses outside the measurement boundary, and any extra control activity. They are not battery-size recommendations.&lt;/p&gt;

&lt;p&gt;Battery sizing must use the worst credible loaded voltage rather than the nominal label alone. Repeat the operating-point analysis at the voltage the ESC will actually see near the minimum allowed state of charge. If no source curve exists at that voltage, test it. Scaling thrust by the square of voltage or scaling current linearly may be tempting, but those approximations can be wrong once ESC duty, motor loading, propeller RPM, and battery resistance interact.&lt;/p&gt;

&lt;p&gt;Log current at the pack and, during development, at selected ESC inputs. The sum of ESC currents need not exactly equal pack current if timestamps, filters, and measurement boundaries differ. Synchronize clocks and document averaging windows before using the data to estimate wiring loss or diagnose an outlier motor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Size the ESC and wiring from waveforms and duration
&lt;/h2&gt;

&lt;p&gt;An ESC should not be chosen by matching one published current number. Confirm supported cell count, continuous and transient current at the installed cooling condition, commutation compatibility, firmware settings, command protocol, telemetry behavior, and protection response. Consider the current step during rapid attitude correction, not just steady hover average. A slow current logger can miss short peaks that heat semiconductors or trigger protection.&lt;/p&gt;

&lt;p&gt;Connector, cable, distribution-board, and solder-joint resistance turn current into localized heat. Measure voltage at both the pack and ESC during a restrained installed test. A few tenths of a volt lost in distribution can alter available control margin while the battery monitor still reports an acceptable pack voltage. Use appropriately rated protection and an independent emergency disconnect on the test stand.&lt;/p&gt;

&lt;p&gt;Do not run an automated script as a flight-safety controller. The code above is offline analysis. Propeller testing requires a mechanically secure stand, guarding, an exclusion zone, remote observation, and a qualified operator. Emergency shutdown must not depend on a browser, wireless link, cloud service, or the analysis process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Treat temperature as a time series
&lt;/h2&gt;

&lt;p&gt;A single temperature cell cannot prove thermal suitability. Record ambient temperature, pressure, motor surface or winding sensor location, ESC sensor location, airflow configuration, command, current, RPM, and time. Plot temperature rise and its slope. If the slope is still positive at the end of the required segment, the system has not demonstrated a stable continuous condition.&lt;/p&gt;

&lt;p&gt;The manufacturer's listed test temperatures were produced under its stated procedure. An installed arm, fairing, payload, or coaxial arrangement can change cooling and recirculation. Copper resistance rises with temperature, battery sag changes with state and temperature, and hot electronics may derate. Validate the hottest expected operating environment and the actual duty cycle. Do not subtract a cool bench ambient from a listed surface temperature and then add that difference to a hot-day forecast as if the system were linear.&lt;/p&gt;

&lt;p&gt;Air density affects the operating point as well. Density altitude is not solved by adding an arbitrary fixed thrust percentage to every mission. Use atmospheric inputs appropriate to the site, then verify the predicted correction against an installed test. If the aircraft must operate across a wide altitude range, store separate validated envelopes or use a model whose error bounds are supported by test data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect the calculation to telemetry and review
&lt;/h2&gt;

&lt;p&gt;The design record and flight log should share stable identifiers for motor model, winding, propeller part and revision, ESC hardware and firmware, battery type, airframe revision, and analysis version. Without those joins, an analyst can observe elevated current but cannot determine which physical configuration produced it.&lt;/p&gt;

&lt;p&gt;At minimum, log battery voltage and current, per-motor command, RPM or ESC telemetry where available, attitude-controller saturation, vehicle mass configuration, ambient conditions, and relevant temperatures. Use monotonic timestamps on the vehicle and preserve clock-correlation metadata for external payloads. Flag sensor dropouts explicitly; zero is a measurement, not a substitute for missing data.&lt;/p&gt;

&lt;p&gt;Define operational signals before flight. Examples include hover power drifting upward relative to comparable flights, persistent RPM divergence at similar commands, reduced command headroom, rising vibration, connector temperature growth, or one ESC reporting repeated protection events. Compare like with like: similar mass, propeller, battery state, wind, altitude, and mission segment. A fleet dashboard that ignores configuration identity can turn normal differences into false alarms and real degradation into averages.&lt;/p&gt;

&lt;p&gt;Review the result as an envelope, not a winner. A candidate can be &lt;code&gt;rejected&lt;/code&gt;, &lt;code&gt;requires_test&lt;/code&gt;, or &lt;code&gt;qualified_for_defined_conditions&lt;/code&gt;. The last state must name those conditions. “Qualified” without voltage, propeller, mass, ambient range, firmware, and duration is not a reusable engineering decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build evidence in stages
&lt;/h2&gt;

&lt;p&gt;Start with document reconciliation: current drawing, winding, propeller, voltage, ESC manual, connector and mounting requirements. Then perform receiving inspection and a no-propeller electrical check. Move to a guarded thrust stand with calibrated thrust, voltage, current, RPM, vibration, and temperature sensing. Reproduce the intended hover and reserve points, including the worst expected loaded voltage and duration.&lt;/p&gt;

&lt;p&gt;Next test the installed but restrained propulsion system. Installation can reveal arm resonance, airflow obstruction, wiring loss, telemetry interference, or controller behavior absent from an isolated stand. Only then conduct a conservative first flight with explicit abort conditions and a limited envelope. Review the complete log before expanding mass, duration, wind, temperature, or altitude.&lt;/p&gt;

&lt;p&gt;Acceptance criteria should have been written before the test. They may include maximum stabilized current at a specified thrust, minimum command reserve, temperature limits at declared sensor locations, vibration thresholds, allowable sensor disagreement, and post-test mechanical inspection. If the evidence fails a criterion, preserve the failure and its configuration. Do not simply rerun until a passing trace appears.&lt;/p&gt;

&lt;p&gt;The MN4010 data illustrates the central lesson: component selection is a constrained, provenance-aware software problem as much as it is a hardware problem. At the same 850 g screening target, a larger 4S propeller can use less published electrical power, while a smaller 6S propeller provides substantially more published thrust headroom. The right choice depends on the aircraft's mission, geometry, uncertainty, power system, thermal envelope, and control requirements. A professional workflow makes every one of those dependencies visible—and refuses to manufacture certainty when the required evidence does not exist.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>python</category>
      <category>robotics</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Stop Comparing UAV Motors at Equal Throttle: A Reproducible MN505-S Bench Workflow</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Sun, 13 Sep 2026 11:58:16 +0000</pubDate>
      <link>https://dev.to/united_uav/stop-comparing-uav-motors-at-equal-throttle-a-reproducible-mn505-s-bench-workflow-5cd4</link>
      <guid>https://dev.to/united_uav/stop-comparing-uav-motors-at-equal-throttle-a-reproducible-mn505-s-bench-workflow-5cd4</guid>
      <description>&lt;p&gt;A motor table is often read as a shopping chart: find the largest thrust number, check that the ESC amperage looks close, and move on. For an aircraft that must fly repeatedly, that is not a propulsion design. The useful question is whether a particular motor, propeller, ESC, battery, mount, and cooling path can deliver the required thrust &lt;em&gt;at the actual operating point&lt;/em&gt;, for the required duration, with measurable margin.&lt;/p&gt;

&lt;p&gt;The randomly selected product for this example is the &lt;a href="https://store.uniteduav.com/products/mn505-s-kv320" rel="noopener noreferrer"&gt;UNITED UAV T-MOTOR MN505-S KV320 Navigator motor&lt;/a&gt;. It provides an unusually useful case study because &lt;a href="https://store.tmotor.com/product/mn505-s-kv320-motor-navigator-type.html" rel="noopener noreferrer"&gt;T-MOTOR publishes separate bench curves&lt;/a&gt; for the KV320 winding with P20×6 and P22×6.6 propellers. Those curves let us demonstrate a mistake that survives many otherwise careful reviews: comparing combinations at equal throttle percentage rather than equal thrust.&lt;/p&gt;

&lt;p&gt;This is a software-and-test-engineering article, not a recommendation that a motor is suitable for any particular aircraft. No flight test, calibrated bench test, or independent endurance measurement was performed for this post. Published numbers are reference data to audit and reproduce. The Python example is an analysis tool for recorded observations; it does not command a motor or an aircraft.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commercial disclosure:&lt;/strong&gt; UNITED UAV Official publishes this article and sells the linked motor. The analysis method is intended to be useful even if you choose a different supplier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define the decision before opening a motor spreadsheet
&lt;/h2&gt;

&lt;p&gt;Start with an aircraft-level requirement rather than a component-level target. For a multirotor, record the maximum takeoff mass, number of lifting rotors, permitted center-of-gravity range, intended hover duration, climb requirement, ambient-temperature envelope, operating altitude, and minimum reserve after a credible degradation. A VTOL needs separate lift, transition, and cruise conditions because the same average mission power can hide a short but severe vertical-load phase.&lt;/p&gt;

&lt;p&gt;A first-pass hover estimate is aircraft weight divided by the number of lifting rotors. It is &lt;em&gt;not&lt;/em&gt; the design thrust per rotor. It omits maneuvering, gust rejection, thrust imbalance, degraded battery voltage, manufacturing variation, and whatever reserve the airframe safety analysis requires. It also assumes every rotor shares load equally. That may not hold when the center of gravity moves with payload or battery placement. Calculate the required operating points from the complete aircraft, then ask which propulsion combination can meet them.&lt;/p&gt;

&lt;p&gt;Write the acceptance criteria before testing. Examples include a maximum measured current at a specified thrust, a maximum stabilized motor and ESC temperature at a specified ambient condition, a minimum control reserve at the worst expected battery voltage, and a vibration threshold under the installed propeller. The numerical limits must come from the airframe design and verified component documentation; borrowing a generic percentage from another aircraft is not engineering evidence.&lt;/p&gt;

&lt;p&gt;The output should be a decision record with the exact motor winding, propeller part number and revision, ESC model and firmware, battery or power-supply configuration, mount, calibration identifiers, environmental conditions, raw data hashes, analysis version, and reviewer. Without configuration identity, a beautiful thrust plot is difficult to reproduce and almost impossible to use safely for a later replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read the published numbers with their conditions attached
&lt;/h2&gt;

&lt;p&gt;The current &lt;a href="https://store.uniteduav.com/products/mn505-s-kv320" rel="noopener noreferrer"&gt;UNITED UAV listing&lt;/a&gt; identifies the MN505-S &lt;strong&gt;KV320&lt;/strong&gt; item separately from other windings. The &lt;a href="https://store.tmotor.com/product/mn505-s-kv320-motor-navigator-type.html" rel="noopener noreferrer"&gt;manufacturer's specification and test table&lt;/a&gt; list 6S LiPo as the rated voltage, 55 A peak current for 180 seconds, 1,300 W maximum power for 180 seconds, approximately 225 g motor mass including cable, and suggested 20–22 inch propellers. The same page identifies IP45 for the motor. These are not whole-aircraft ratings. They do not tell you that a chosen ESC, connector, battery, flight controller, or airframe can tolerate rain, dust, heat, or a 180-second peak.&lt;/p&gt;

&lt;p&gt;The published table gives these KV320 points:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Propeller&lt;/th&gt;
&lt;th&gt;Throttle label&lt;/th&gt;
&lt;th&gt;Voltage&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Thrust&lt;/th&gt;
&lt;th&gt;Input power&lt;/th&gt;
&lt;th&gt;Listed efficiency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;P20×6&lt;/td&gt;
&lt;td&gt;70%&lt;/td&gt;
&lt;td&gt;24.03 V&lt;/td&gt;
&lt;td&gt;16.30 A&lt;/td&gt;
&lt;td&gt;2,921 g&lt;/td&gt;
&lt;td&gt;393 W&lt;/td&gt;
&lt;td&gt;7.44 g/W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P22×6.6&lt;/td&gt;
&lt;td&gt;60%&lt;/td&gt;
&lt;td&gt;23.99 V&lt;/td&gt;
&lt;td&gt;15.20 A&lt;/td&gt;
&lt;td&gt;2,968 g&lt;/td&gt;
&lt;td&gt;364 W&lt;/td&gt;
&lt;td&gt;8.15 g/W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P20×6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;23.96 V&lt;/td&gt;
&lt;td&gt;38.50 A&lt;/td&gt;
&lt;td&gt;5,372 g&lt;/td&gt;
&lt;td&gt;921 W&lt;/td&gt;
&lt;td&gt;5.83 g/W&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P22×6.6&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;23.87 V&lt;/td&gt;
&lt;td&gt;52.40 A&lt;/td&gt;
&lt;td&gt;6,680 g&lt;/td&gt;
&lt;td&gt;1,249 W&lt;/td&gt;
&lt;td&gt;5.35 g/W&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first two rows are near the &lt;em&gt;same thrust&lt;/em&gt;, even though their throttle labels differ by ten points. In those specific published conditions, P22×6.6 produces about 47 g more thrust while drawing about 29 W less electrical power. That is a useful clue for a roughly 3 kg-per-rotor design point, not proof that P22×6.6 is always superior. It may have different mechanical clearance, inertia, control response, vibration, or high-temperature behavior. The last two rows instead show why “more maximum thrust” carries a cost: the larger propeller's published full-throttle point is 52.4 A and 1,249 W, close to the motor's listed 55 A and 1,300 W 180-second figures. Treat those maxima as boundary data, not a continuous operating prescription.&lt;/p&gt;

&lt;p&gt;The manufacturer notes an ambient temperature of 9.5 °C for the bench curves and describes motor-surface temperature after a ten-minute full-throttle run. Its page also labels peak current and maximum power as 180-second values. Do not resolve that duration mismatch by assuming a ten-minute high-power rating. Ask the manufacturer which conditions, sensors, and duty limits apply to the exact combination, then validate the intended duty cycle locally. A cool laboratory is not a hot installed nacelle.&lt;/p&gt;

&lt;p&gt;A quick arithmetic check is worthwhile. At the P20×6 70% row, 24.03 V × 16.30 A is about 392 W, consistent with the listed 393 W after rounding. Thrust divided by listed power is about 7.43 g/W, consistent with 7.44 g/W. These checks cannot certify the test, but they catch transcription or unit errors before the data enters a selection model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare at equal thrust, not equal command
&lt;/h2&gt;

&lt;p&gt;Throttle percentage is a command coordinate, not an aerodynamic requirement. Two ESCs can map the same percentage to different electrical timing and speed; two propellers impose different torque on the same motor. Even within one manufacturer's table, equal percentages do not imply equal thrust, current, efficiency, or thermal load.&lt;/p&gt;

&lt;p&gt;For candidate comparison, choose a target thrust derived from the aircraft load case. Interpolate within a measured curve only between nearby points of the &lt;em&gt;same motor, propeller, voltage class, and test setup&lt;/em&gt;. Do not extrapolate beyond its measured range. Report the bracket points and interpolation method with the result. If battery voltage differs materially between curves, normalize with an appropriate validated model or repeat the test at matched voltage rather than silently treating them as equal.&lt;/p&gt;

&lt;p&gt;Near 3 kg, the two rows above are close enough to illustrate the principle without pretending to be a high-precision interpolation. At 70% command, P20×6 reports 2,921 g and 393 W. At 60% command, P22×6.6 reports 2,968 g and 364 W. A chart organized by throttle would put those observations in different columns and could obscure the comparison. A chart with thrust on the x-axis and electrical input power on the y-axis makes the design decision visible.&lt;/p&gt;

&lt;p&gt;At other load points, the answer can change. Propeller aerodynamic efficiency, motor copper loss, ESC behavior, and cooling all vary with torque and speed. A single g/W value is not a universal property of the motor. The candidate should be evaluated over the mission's &lt;em&gt;thrust distribution&lt;/em&gt;: minutes at hover, seconds during climb, time in transition, and the battery voltage at each phase. One row at full throttle should never be used to predict an entire flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instrument the bench so the data can be trusted
&lt;/h2&gt;

&lt;p&gt;A useful bench has a restrained, guarded motor mount; a calibrated thrust sensor; voltage and current measurement at a declared point in the electrical path; RPM measurement; motor and ESC temperature sensors; ambient temperature; and a record of propeller identity. If torque is measured, log the torque-sensor calibration and sign convention. If it is not measured, do not infer mechanical efficiency from electrical input and thrust alone.&lt;/p&gt;

&lt;p&gt;Specify sensor placement. Battery-terminal voltage is not necessarily ESC-input voltage after cables, connectors, and distribution hardware. An ESC's reported current may be filtered or estimated differently from a calibrated shunt. An infrared image of a shiny rotor can be misleading because emissivity and viewing angle are uncontrolled. For temperature trends, use repeatable sensor locations and document whether the value is winding, stator case, rotor surface, ESC case, or ambient air.&lt;/p&gt;

&lt;p&gt;Define a common timebase. Record raw sensor timestamps, sample rates, clock offsets, and the aggregation window used for each test point. A row that joins thrust at second 12 with current at second 18 can create a false efficiency result during a throttle ramp. A stable-point result should be computed from a window in which command, RPM, thrust, and current have settled. Keep the raw series so an analyst can see oscillation or heating that a single average hides.&lt;/p&gt;

&lt;p&gt;Calibrate and zero the load cell before each session. Record the thrust-stand orientation, fixture geometry, propeller plane, and nearby surfaces. Recirculation, wall proximity, ground effect, and test-stand blockage can move results. Compare candidate propellers on the same fixture under a controlled procedure. Randomize test order or repeat a baseline combination at the end; otherwise ambient warming, battery depletion, or sensor drift may look like a propeller effect.&lt;/p&gt;

&lt;p&gt;Propeller testing is hazardous. Use a suitable enclosure and exclusion zone, secure the stand, keep people out of the propeller plane, provide an independent emergency power disconnect, and have a qualified operator supervise the test. A browser dashboard or Python script should only observe the stand. Flight-critical protection and physical emergency controls must not depend on a cloud service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persist a minimal data contract
&lt;/h2&gt;

&lt;p&gt;A row should be self-describing enough to survive export from the original logging system. A compact JSON record can carry the exact configuration, measurements, and quality flags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mn505s-kv320-p22x66-20260913-b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"point_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hover-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"motor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MN505-S KV320"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"propeller"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"T-MOTOR P22x6.6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"esc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"record-exact-model-and-firmware"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6S test supply"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"window_start_utc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-13T03:10:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"window_duration_s"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"voltage_v"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;23.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"current_a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;15.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thrust_g"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2968&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rpm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3790&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"motor_surface_c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"esc_case_c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ambient_c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quality_flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"illustrative_from_manufacturer_table"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sample values reproduce one published manufacturer row; the run ID and timestamp are illustrative, not a claim that UNITED UAV performed this test. Null temperatures mean “not available in this row,” not zero degrees. A production record should include sensor IDs, calibration dates, fixture revision, raw-data URI and checksum, operator, and the exact source document revision. Use a separate field for a vendor-published row versus a measurement made by your team. Otherwise downstream code may accidentally merge literature data and acceptance-test evidence.&lt;/p&gt;

&lt;p&gt;Keep current and voltage at the same electrical boundary. If a regulator, distribution board, or cable lies between their measurement points, their product does not represent the motor/ESC input power you think it does. Record whether the power measurement includes ESC loss. “Efficiency” in g/W is a useful thrust-per-electrical-input metric, but it is not motor shaft efficiency and should not be labeled as such.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate observations before ranking candidates
&lt;/h2&gt;

&lt;p&gt;The following dependency-free Python function checks the minimum numerical invariants in a list of bench records and selects a point near a target thrust. It intentionally refuses to compare records outside a declared thrust tolerance. It does not extrapolate, interpolate, or claim a production safety decision.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;isfinite&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rank_near_target&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerance_g&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target_g must be positive and finite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tolerance_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;tolerance_g&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tolerance_g must be positive and finite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;rejected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;run_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;point_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schema_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;motor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing configuration identity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;voltage_v&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;thrust&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isfinite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thrust&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non-finite measurement&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;thrust&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non-positive measurement&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;power_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
            &lt;span class="n"&gt;efficiency_g_per_w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;thrust&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;power_w&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thrust&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tolerance_g&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;outside target thrust window&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;propeller&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thrust_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;thrust&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;power_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;g_per_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;efficiency_g_per_w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distance_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thrust&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target_g&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;identity&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

    &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;power_w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distance_g&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ranked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rejected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rejected&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the two published near-3 kg observations, the function reports roughly 392 W for P20×6 and 365 W for P22×6.6 when calculated from the rounded voltage and current fields. That slight difference from the table's 393 W and 364 W is a rounding reminder, not a reason to “correct” the manufacturer's data. Retain both the published value and the recomputed value with provenance if exact reconciliation matters.&lt;/p&gt;

&lt;p&gt;This small function is not enough for an acceptance pipeline. Production code should validate units, schema revisions, unique identities, calibration validity, timestamps, sensor saturation, and the distinction between measured and source-derived records. It should keep rejected rows in an auditable quarantine set rather than silently dropping them. Property-based tests can generate zero, negative, infinite, NaN, duplicate, and out-of-range inputs to prove that ranking never accepts them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Model uncertainty instead of hiding it
&lt;/h2&gt;

&lt;p&gt;Any thrust/power comparison needs an uncertainty budget. The load cell contributes calibration and repeatability error. Current and voltage sensors contribute gain, offset, and timing error. The battery or power supply can sag. The stand can alter flow. Ambient density changes with temperature, pressure, and humidity. Propeller manufacturing tolerances and balance change both thrust and vibration. The total is not necessarily the simple sum of each individual sensor tolerance, but it should be documented.&lt;/p&gt;

&lt;p&gt;Report at least the number of repeats, mean, spread, and test conditions for each target point. If the apparent power advantage is smaller than the combined uncertainty or varies by test order, label the result inconclusive. If the candidate is selected on a 29 W difference near 3 kg of thrust, repeat the measurements with a calibrated system before using that difference in an endurance promise.&lt;/p&gt;

&lt;p&gt;Thermal uncertainty is especially easy to underestimate. The manufacturer's surface-temperature figures are tied to its bench and a cool listed ambient. A motor installed close to an arm, fairing, spray system, or tether hardware may have a different cooling path. Winding temperature can differ from a case or rotor measurement. Build a controlled duration test at the expected thrust, then review the temperature slope and stabilization behavior. If temperature is still rising near the end of the planned duty interval, a single final value does not establish continuous suitability.&lt;/p&gt;

&lt;p&gt;Do not extrapolate “temperature rise above ambient” linearly from one laboratory point to a hot field site. Air density, propeller loading, convective cooling, ESC loss, and motor resistance can all change. A hot-condition test or validated thermal model is needed for a high-consequence mission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Integrate the whole power path
&lt;/h2&gt;

&lt;p&gt;A 55 A motor peak-current figure does not authorize a 55 A aircraft design. Confirm the ESC's voltage and continuous/transient current capability for the exact firmware and cooling condition. Confirm connector and cable ampacity, distribution-board heating, battery discharge limits, fuse or protection strategy where applicable, and voltage sag at the worst expected state of charge. Log at both battery and ESC boundaries if cable loss matters.&lt;/p&gt;

&lt;p&gt;Mechanical integration is equally specific. The manufacturer's page lists an approximately 55.6 × 38.9 mm motor envelope, 225 g mass including cable, and a warning to keep mounting-thread engagement within its stated limit. Obtain the current drawing, verify bolt pattern and screw depth, and inspect for any contact with windings or rotating parts. Validate propeller retention, clearance, rotation direction, and arm stiffness. A well-matched electrical system can still fail because the mount resonates or a screw damages the motor.&lt;/p&gt;

&lt;p&gt;The motor's IP45 claim does not transfer to the ESC, connectors, bearings after wear, or the complete aircraft. Environmental qualification should consider the installed system and its maintenance state. Record water or dust exposure, cleaning method, corrosion findings, bearing play, cable damage, vibration trend, and current drift over service life. A numerical MTBF claim, where listed, is not a substitute for a fleet-specific inspection program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the bench result into a flight-readiness gate
&lt;/h2&gt;

&lt;p&gt;A sensible sequence is receiving inspection; document and connector review; no-propeller rotation check; guarded stand test; installed restrained ground test; conservative first flight; and log review before expanding the envelope. Each transition should have written entry conditions and stop criteria. The first flight should not be asked to discover a propeller mismatch that a guarded bench could have found.&lt;/p&gt;

&lt;p&gt;For an installed multirotor, compare equivalent arms at a controlled operating condition. Look for differences in current at similar thrust command, RPM where available, ESC temperature, motor temperature, and vibration. An outlier may indicate a damaged propeller, bearing issue, cable resistance, ESC configuration difference, or structural resonance. Do not assume that swapping only the motor isolates the cause; change one variable at a time and preserve the configuration history.&lt;/p&gt;

&lt;p&gt;After flight, compare the measured thrust-demand distribution with the load cases used in selection. If the aircraft spends more time at high command than expected, or one arm consistently works harder, revisit the mass, center of gravity, propeller, and control assumptions. A propulsion design is validated by the operating envelope it actually sees, not merely by a catalog point it once matched.&lt;/p&gt;

&lt;p&gt;The engineering takeaway is narrow but important: use the MN505-S KV320 manufacturer's table as a starting dataset, not a certificate of aircraft suitability. Compare propellers at equal required thrust, carry measurement provenance and uncertainty through analysis, validate the thermal and electrical path at the intended duty cycle, and require independent acceptance evidence before flight. That workflow is portable to other UAV motors—and more valuable than any isolated “maximum thrust” number.&lt;/p&gt;

</description>
      <category>hardware</category>
      <category>performance</category>
      <category>tools</category>
    </item>
    <item>
      <title>Designing a Dual-Sensor Data Contract for a Thermal VTOL Pipeline</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:30:37 +0000</pubDate>
      <link>https://dev.to/united_uav/designing-a-dual-sensor-data-contract-for-a-thermal-vtol-pipeline-2am5</link>
      <guid>https://dev.to/united_uav/designing-a-dual-sensor-data-contract-for-a-thermal-vtol-pipeline-2am5</guid>
      <description>&lt;p&gt;A dual-sensor UAV can produce visible video, thermal frames, aircraft position, attitude, gimbal angles, target detections, and operator events at different rates. The difficult part is not collecting every stream. It is proving which observations belong together without inventing precision that the source system never provided.&lt;/p&gt;

&lt;p&gt;This article develops a data contract for that problem. It uses the &lt;a href="https://store.uniteduav.com/products/uvh2-dual-sensor-fixed-wing-vtol-drone?utm_source=dev.to&amp;amp;utm_medium=community&amp;amp;utm_campaign=dev_uvh2_data_contract_20260828"&gt;UNITED UAV UVH2 dual-sensor fixed-wing VTOL drone&lt;/a&gt; as a concrete product context. The current listing describes a K8T-V2 payload with an 8 MP visible channel, 4K visible recording, a native 640 × 512 thermal detector, processed 1280 × 1024 thermal super-resolution, and Ethernet, serial, and SBUS interfaces. It also identifies the communication protocol as proprietary.&lt;/p&gt;

&lt;p&gt;That last detail defines the software boundary: obtain the supported interface-control document and use an approved adapter. The schemas and code below describe a downstream observation pipeline. They are not the UVH2 command protocol, do not reverse-engineer it, and must not be placed in a flight-critical control path.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Commercial disclosure:&lt;/strong&gt; UNITED UAV Official publishes this technical article and links to a product sold through the UNITED UAV store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why a filename pair is not a data contract
&lt;/h2&gt;

&lt;p&gt;A common first implementation writes &lt;code&gt;visible_001.jpg&lt;/code&gt; and &lt;code&gt;thermal_001.jpg&lt;/code&gt;, then assumes the matching number proves that the files were captured together. That assumption breaks when one encoder drops a frame, a camera restarts, a buffer flushes late, or a transmission path reorders messages.&lt;/p&gt;

&lt;p&gt;The pipeline needs to preserve three different facts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;what each source reported;&lt;/li&gt;
&lt;li&gt;how the integration correlated the sources;&lt;/li&gt;
&lt;li&gt;how confident the integration is in that correlation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those facts should remain visible to every downstream consumer. An inspection model may accept a 200 ms pairing tolerance. A moving-target workflow may require a much tighter limit. A reviewer should never have to infer that tolerance from filenames or application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Define an immutable capture bundle
&lt;/h2&gt;

&lt;p&gt;Treat every correlated observation as an immutable bundle. Large media files stay in object storage; the event carries their checksums, dimensions, formats, timestamps, and references.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"capture_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01J6FGP8B0V5TGQ1M9A2X4K7CZ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vehicle_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uvh2-field-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mission_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"roof-baseline-2026-08-28-r3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"observed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-28T06:18:42.481Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_clock_ns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5842284190031&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"clock_epoch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload-boot-0194"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"visible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"object_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3://uav-raw/2026/08/28/visible/01J6F.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3840&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"encoding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jpeg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_sequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18422&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"thermal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"object_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s3://uav-raw/2026/08/28/thermal/01J6F.bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"representation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"native_detector"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"source_sequence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9211&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"correlation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source_clock_nearest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"delta_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;18.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"max_allowed_delta_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quality"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accepted"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pose"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"position_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aircraft_telemetry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"attitude_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aircraft_telemetry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gimbal_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payload_telemetry"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sample_delta_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;11.2&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"adapter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.3.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"interface_document_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor-approved-revision"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example deliberately avoids claiming that every payload exposes the same fields. The adapter should record &lt;code&gt;null&lt;/code&gt; plus a reason when the supported interface does not provide a value. Fabricated defaults are more dangerous than missing data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use two clocks, not one
&lt;/h2&gt;

&lt;p&gt;Wall-clock time answers when an observation occurred in a shared timeline. A monotonic source clock answers how events were ordered inside one boot session. Both are necessary.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;observed_at&lt;/code&gt; should use UTC with an explicit offset. &lt;code&gt;source_clock_ns&lt;/code&gt; should never move backward during one &lt;code&gt;clock_epoch&lt;/code&gt;. A payload restart creates a new epoch even if its sequence counter returns to zero. The ingestion service can then distinguish a reboot from delayed delivery.&lt;/p&gt;

&lt;p&gt;Store clock-quality metadata separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;synchronization source, such as GNSS, PTP, NTP, or platform-provided time;&lt;/li&gt;
&lt;li&gt;last successful synchronization time;&lt;/li&gt;
&lt;li&gt;estimated offset and uncertainty;&lt;/li&gt;
&lt;li&gt;detection of an unexpected time step;&lt;/li&gt;
&lt;li&gt;adapter receive time and storage time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A precise-looking timestamp is not proof of accurate synchronization. If uncertainty is unknown, say so and lower the correlation quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep native and derived thermal imagery distinct
&lt;/h2&gt;

&lt;p&gt;The UVH2 listing separates the native 640 × 512 detector from 1280 × 1024 thermal super-resolution output. A data model should preserve that distinction. Upscaled or enhanced output is useful, but it is not a higher-resolution detector measurement.&lt;/p&gt;

&lt;p&gt;Use an explicit representation field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;native_detector
processed_super_resolution
colorized_preview
visible_thermal_overlay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Derived assets should reference their parents and record the processing version, parameters, and checksum. Never overwrite the native input with an enhanced image. If a later model produces a different result, both versions should remain reproducible.&lt;/p&gt;

&lt;p&gt;Thermal observation also does not automatically mean calibrated temperature measurement. If radiometric values are required, the contract needs documented units, calibration state, emissivity handling, reflected-temperature assumptions, measurement range, accuracy, and invalid-pixel behavior. When the source interface does not provide those fields, the pipeline should label the asset as qualitative thermal imagery.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make partial pairs first-class events
&lt;/h2&gt;

&lt;p&gt;A dual-sensor pipeline must tolerate one channel arriving late or not arriving at all. Use an explicit lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;received_visible_only
received_thermal_only
paired_pending_validation
paired_accepted
paired_rejected
expired_unpaired
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not keep an event in memory forever while waiting for its partner. Persist the first observation, start a bounded correlation window, and finalize it as &lt;code&gt;expired_unpaired&lt;/code&gt; when the deadline passes. A late partner can create a new reconciliation record without rewriting history.&lt;/p&gt;

&lt;p&gt;The difference matters operationally. A missing visible frame may still leave a useful thermal observation. A missing thermal frame may still preserve visual context. Downstream systems can decide whether a partial bundle is usable, but they should not be told that a complete pair exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate at ingestion
&lt;/h2&gt;

&lt;p&gt;Validation should reject malformed events before analytics or indexing. A dependency-free Python example can enforce some basic invariants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;


&lt;span class="n"&gt;ALLOWED_THERMAL_REPRESENTATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;native_detector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processed_super_resolution&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;colorized_preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible_thermal_overlay&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;+00:00&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observed_at requires a timezone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;astimezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_bundle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;schema_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unsupported schema_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capture_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vehicle_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mission_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clock_epoch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;missing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;parse_utc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observed_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;correlation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;correlation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;correlation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delta_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;correlation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_allowed_delta_ms&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid correlation timing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;correlation&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quality&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepted&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accepted pair exceeds timing limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;thermal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thermal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;thermal&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;thermal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;representation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOWED_THERMAL_REPRESENTATIONS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown thermal representation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;thermal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;asset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; dimensions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sha256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;invalid &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; checksum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production validation should use a versioned schema format and bounded payload sizes. Keep rejected metadata in a quarantine stream with reason codes; do not silently discard it or mix it with accepted observations. Media decoders need their own resource limits because image dimensions in metadata cannot be trusted until the bytes are inspected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make idempotency content-aware
&lt;/h2&gt;

&lt;p&gt;Networks retry. Edge computers reboot. Upload workers receive the same message twice. A stable event identity prevents those normal behaviors from multiplying observations.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;capture_id&lt;/code&gt; as the business identity and verify that any repeat has the same immutable digest. The outcomes should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;same ID, same digest: harmless retry;&lt;/li&gt;
&lt;li&gt;same ID, different digest: conflict requiring investigation;&lt;/li&gt;
&lt;li&gt;new ID, same source sequences and clock epoch: probable duplicate from an upstream identity defect;&lt;/li&gt;
&lt;li&gt;new ID, same media checksums: duplicate asset or repeated capture requiring a documented decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write the event and its asset references atomically, or keep an explicit state such as &lt;code&gt;metadata_committed_assets_pending&lt;/code&gt;. A database row that points to missing media is not a complete ingestion result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate observation from inference
&lt;/h2&gt;

&lt;p&gt;Target recognition, tracking, anomaly scoring, mapping, and thermography interpretation are downstream results. Store them as derived records that point back to the immutable capture bundle. Each result should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;model or algorithm name and version;&lt;/li&gt;
&lt;li&gt;model-file checksum;&lt;/li&gt;
&lt;li&gt;input capture ID and asset representation;&lt;/li&gt;
&lt;li&gt;thresholds and preprocessing version;&lt;/li&gt;
&lt;li&gt;output coordinate system;&lt;/li&gt;
&lt;li&gt;confidence and known limitations;&lt;/li&gt;
&lt;li&gt;reviewer state when a human decision is required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not write a detection back into the source event as if the camera observed a semantic truth. The camera produced pixels and metadata; an algorithm produced an interpretation. That separation makes reprocessing, disagreement analysis, and incident review possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect operational and personal data
&lt;/h2&gt;

&lt;p&gt;Visible and thermal UAV data may reveal people, vehicles, infrastructure, precise locations, schedules, and operational patterns. Minimize collection to the approved mission, encrypt data in transit and at rest, use short-lived scoped credentials, and log access to original media.&lt;/p&gt;

&lt;p&gt;Keep raw media separate from public application paths. Derived previews should remove unnecessary metadata and use redaction when the workflow requires it. Retention policy should apply to raw files, thumbnails, detections, exports, backups, and cached edge copies—not only to the primary database.&lt;/p&gt;

&lt;p&gt;The adapter that reads the supported payload interface should have no broader command permission than it needs. Observation ingestion must not become an undocumented route to aircraft or gimbal control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the failure modes
&lt;/h2&gt;

&lt;p&gt;Before relying on the pipeline, replay at least these scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;visible frames arrive before, after, and without thermal frames;&lt;/li&gt;
&lt;li&gt;sequence counters restart after a payload reboot;&lt;/li&gt;
&lt;li&gt;wall time jumps while the monotonic clock continues;&lt;/li&gt;
&lt;li&gt;a processed thermal asset is incorrectly labelled as native;&lt;/li&gt;
&lt;li&gt;the same capture ID arrives with a different checksum;&lt;/li&gt;
&lt;li&gt;object storage succeeds but metadata commit fails, and vice versa;&lt;/li&gt;
&lt;li&gt;the adapter version changes during a mission;&lt;/li&gt;
&lt;li&gt;media is truncated, oversized, or declares false dimensions;&lt;/li&gt;
&lt;li&gt;telemetry and gimbal samples are older than the configured limit;&lt;/li&gt;
&lt;li&gt;a late event arrives after the correlation window expires.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each test, define the expected event state and evidence before running it. A pipeline that merely avoids crashing is not necessarily correct. It should expose why a bundle was accepted, rejected, left partial, or quarantined.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical readiness checklist
&lt;/h2&gt;

&lt;p&gt;Before production use, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the delivered payload and approved interface document match the adapter version;&lt;/li&gt;
&lt;li&gt;every boot session has a distinct clock epoch;&lt;/li&gt;
&lt;li&gt;wall-clock accuracy and monotonic ordering are recorded separately;&lt;/li&gt;
&lt;li&gt;pairing tolerance is explicit and mission-appropriate;&lt;/li&gt;
&lt;li&gt;native and processed thermal representations cannot be confused;&lt;/li&gt;
&lt;li&gt;partial and late observations have bounded, auditable states;&lt;/li&gt;
&lt;li&gt;media bytes are verified independently of metadata;&lt;/li&gt;
&lt;li&gt;retries are idempotent and digest conflicts stop processing;&lt;/li&gt;
&lt;li&gt;inference outputs never overwrite source observations;&lt;/li&gt;
&lt;li&gt;retention, redaction, encryption, and access logging cover edge and cloud copies.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A strong dual-sensor pipeline does not hide uncertainty. It turns timing, derivation, missing data, and interface revision into fields that can be tested. That makes the resulting data more useful to developers and more defensible to operators and reviewers.&lt;/p&gt;

&lt;p&gt;Review the current &lt;a href="https://store.uniteduav.com/products/uvh2-dual-sensor-fixed-wing-vtol-drone?utm_source=dev.to&amp;amp;utm_medium=community&amp;amp;utm_campaign=dev_uvh2_data_contract_20260828"&gt;UVH2 product page&lt;/a&gt; for the published platform and payload starting point. For a real integration, request the current K8T-V2 interface-control document, supported command and telemetry definitions, connector details, licensing terms, sample files, and a configuration-specific acceptance procedure.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Designing a Fail-Closed API Integration for an Autonomous Drone Dock</title>
      <dc:creator>UNITED UAV Official</dc:creator>
      <pubDate>Fri, 28 Aug 2026 11:23:30 +0000</pubDate>
      <link>https://dev.to/united_uav/designing-a-fail-closed-api-integration-for-an-autonomous-drone-dock-4jdj</link>
      <guid>https://dev.to/united_uav/designing-a-fail-closed-api-integration-for-an-autonomous-drone-dock-4jdj</guid>
      <description>&lt;p&gt;Autonomous drone docks sit at an awkward boundary: they look like IoT devices, but a command can eventually affect a real aircraft. That makes a conventional "send request and retry on failure" integration unsafe. The software must distinguish observation from authorization, reject stale or ambiguous commands, and fail closed whenever state cannot be proven.&lt;/p&gt;

&lt;p&gt;This article presents a reference architecture for that integration problem. It uses the &lt;a href="https://store.uniteduav.com/products/docking-station-uk03?utm_source=dev.to&amp;amp;utm_medium=community&amp;amp;utm_campaign=dev_uk03_api_20260828"&gt;UNITED UAV UK03 drone docking station&lt;/a&gt; as a concrete product context because its current listing includes API availability, RTK, internal and external cameras, weather sensing, optional edge computing, optional mesh networking, and optional SIM-based 4G. The listing does not publish an endpoint schema, so every endpoint and payload below is deliberately illustrative. A production implementation must use the current vendor documentation and the approved aircraft-management workflow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Commercial disclosure:&lt;/strong&gt; UNITED UAV Official publishes this technical article and links to a product sold through the UNITED UAV store.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Start by separating four kinds of state
&lt;/h2&gt;

&lt;p&gt;Many integrations become dangerous because they compress several questions into one boolean such as &lt;code&gt;ready: true&lt;/code&gt;. A safer model records at least four independent states:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Observed state&lt;/strong&gt; — what the dock, aircraft, weather sensors, and network last reported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validated state&lt;/strong&gt; — whether those observations are recent, internally consistent, and within the operating envelope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorized intent&lt;/strong&gt; — which mission was approved, by whom, for which aircraft, site, route, and time window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executed state&lt;/strong&gt; — what the vendor platform accepted and what later telemetry proves actually happened.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A positive observation is not authorization. An accepted API response is not proof of aircraft motion. A dashboard that has stopped updating is not evidence that conditions remain unchanged. Keeping these states separate makes the system easier to audit and prevents a stale green status from becoming a launch decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put an adapter in front of the vendor API
&lt;/h2&gt;

&lt;p&gt;Do not let schedulers, dashboards, or business applications call the dock API directly. Put a narrow adapter between them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Planner -&amp;gt; Intent Store -&amp;gt; Safety Gate -&amp;gt; Dock Adapter -&amp;gt; Vendor Platform
              |               |              |
              v               v              v
          Audit Log      State Snapshot   Readback Worker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter should expose only the small set of operations the organization has approved. It can normalize vendor-specific status codes, enforce request IDs, apply timeouts, and redact secrets from logs. If the vendor API changes, only the adapter should need to understand the new schema.&lt;/p&gt;

&lt;p&gt;This boundary is also where command and telemetry permissions can be separated. A monitoring service may need read-only access to weather, dock health, charging state, and alarms. It should not automatically inherit permission to open the dock, schedule a route, or dispatch an aircraft.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make every command an expiring intent
&lt;/h2&gt;

&lt;p&gt;A mission request should be an immutable record, not a transient button click. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"intent_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"01J6E7Y9M2K8Q4R5T6V7W8X9ZA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"site_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"solar-north-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aircraft_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uis220-07"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mission_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved_by"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ops-review-queue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"not_before"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-28T02:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-28T02:10:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"required_state_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1842&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;intent_id&lt;/code&gt; becomes the idempotency key. &lt;code&gt;mission_revision&lt;/code&gt; prevents an older route from being dispatched after an edit. &lt;code&gt;expires_at&lt;/code&gt; prevents a delayed queue message from starting a mission outside its approval window. &lt;code&gt;required_state_revision&lt;/code&gt; binds the decision to a particular validated snapshot instead of whatever state happens to exist later.&lt;/p&gt;

&lt;p&gt;If the vendor API supports an idempotency header, pass the intent ID through. If it does not, the adapter must keep its own durable command ledger and query vendor state before deciding whether a retry is safe. Never generate a new intent ID merely to make an uncertain request succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate freshness before values
&lt;/h2&gt;

&lt;p&gt;A weather value can be numerically safe and operationally useless if it is old. Every safety-relevant observation should carry three timestamps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;when the sensor measured it;&lt;/li&gt;
&lt;li&gt;when the platform received it;&lt;/li&gt;
&lt;li&gt;when the integration read it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The safety gate should reject missing timestamps, future timestamps beyond an allowed clock-skew window, and observations older than the system's documented freshness limit. Apply the same rule to dock-door state, aircraft-in-place detection, charging state, RTK status, network health, and camera availability.&lt;/p&gt;

&lt;p&gt;A compact TypeScript-style check might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;GateResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;snapshotRevision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nowMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;GateResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nowMs&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observedAtMs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;snapshot_stale&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aircraftInPlace&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aircraft_not_confirmed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeAlarmCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;active_alarm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weather&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataQualityOk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weather_quality_unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commandPathHealthy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command_path_unhealthy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maintenanceLock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;maintenance_lock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reasons&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;snapshotRevision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example intentionally does not hard-code wind, temperature, rainfall, or battery thresholds. Those limits must come from the most restrictive verified value across the aircraft, payload, dock configuration, route, regulation, and operator procedure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a command state machine
&lt;/h2&gt;

&lt;p&gt;A boolean &lt;code&gt;sent&lt;/code&gt; field cannot represent an external command safely. Use explicit states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prepared
  -&amp;gt; rejected_prewrite
  -&amp;gt; dispatch_requested
  -&amp;gt; accepted_unverified
  -&amp;gt; verified
  -&amp;gt; outcome_uncertain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;rejected_prewrite&lt;/code&gt; means no external write occurred. It is safe to prepare a new intent after correcting the reason. &lt;code&gt;accepted_unverified&lt;/code&gt; means the vendor acknowledged the request, but independent state has not yet proved the result. &lt;code&gt;outcome_uncertain&lt;/code&gt; means a timeout or connection loss happened after transmission may have begun. That state must trigger readback, not a blind retry.&lt;/p&gt;

&lt;p&gt;Persist the transition before and after the external call. The log should contain the intent ID, payload digest, vendor request correlation ID when available, response status, timestamps, adapter version, state revision, and redacted error class. It should never contain API keys, session tokens, or complete signed URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read back the effect independently
&lt;/h2&gt;

&lt;p&gt;After a command is accepted, query the authoritative state surface. For a scheduled mission, verify the mission identifier, route revision, aircraft, dock, planned time, and enabled status. For a door or charging operation, verify the resulting hardware state and its observation timestamp.&lt;/p&gt;

&lt;p&gt;Use a different read path when the platform provides one. For example, a command endpoint may acknowledge queueing while a mission-status endpoint reports execution. Telemetry can then confirm physical progress. These layers answer different questions and should not be treated as duplicates.&lt;/p&gt;

&lt;p&gt;A successful readback should be narrow and deterministic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accepted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markAccepted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;correlationId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finalState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readMission&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accepted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;missionId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intentId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intent_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revision&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mission_revision&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aircraftId&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aircraft_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ledger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;markVerified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;intent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any assertion fails, preserve both records and escalate. Do not overwrite the mismatch with the newest response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep humans at the right boundary
&lt;/h2&gt;

&lt;p&gt;Human approval should apply to a complete, reviewable mission intent. The reviewer should see the route revision, aircraft and payload, launch window, forecast and live weather, site status, airspace or operating approval, expected communications path, and defined abort behavior.&lt;/p&gt;

&lt;p&gt;The integration may automate collection, validation, expiry, and readback. It should not silently broaden an approval when a route, aircraft, payload, time window, safety limit, or site changes. Those changes create a new revision and may require a new approval.&lt;/p&gt;

&lt;p&gt;This distinction also helps during incidents. Operators can determine whether a bad outcome came from incorrect source data, a validation defect, an authorization error, an adapter failure, vendor-platform behavior, or a physical-system fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design network loss as a normal state
&lt;/h2&gt;

&lt;p&gt;The UK03 listing describes optional 4G and mesh capabilities, but multiple bearers do not make a network infallible. Define behavior for at least these cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the adapter cannot reach the vendor platform before dispatch;&lt;/li&gt;
&lt;li&gt;the request times out after bytes may have been transmitted;&lt;/li&gt;
&lt;li&gt;telemetry continues but the command channel is unavailable;&lt;/li&gt;
&lt;li&gt;the dock is reachable but the aircraft link is degraded;&lt;/li&gt;
&lt;li&gt;the WAN fails during a mission;&lt;/li&gt;
&lt;li&gt;buffered events arrive out of order after reconnection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For each case, document which component owns the safe response. Flight-critical failsafes belong in the validated aircraft and approved control system, not in a remote web service. The external integration should observe and report those states without pretending it can replace them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test faults before unattended operation
&lt;/h2&gt;

&lt;p&gt;A staging environment is useful, but it is not enough. The production configuration should go through controlled acceptance tests with the real dock, supported aircraft, network path, and management platform. Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;duplicate command delivery with the same intent ID;&lt;/li&gt;
&lt;li&gt;an expired intent arriving late;&lt;/li&gt;
&lt;li&gt;a route revision changing after approval;&lt;/li&gt;
&lt;li&gt;stale weather and dock-status observations;&lt;/li&gt;
&lt;li&gt;API timeout before and after request transmission;&lt;/li&gt;
&lt;li&gt;loss and recovery of WAN, RTK corrections, and telemetry;&lt;/li&gt;
&lt;li&gt;an aircraft not correctly detected in the dock;&lt;/li&gt;
&lt;li&gt;an active maintenance lock or unresolved alarm;&lt;/li&gt;
&lt;li&gt;a readback that disagrees with the accepted command;&lt;/li&gt;
&lt;li&gt;adapter restart during every state-machine transition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Record the expected safe state and pass criterion before each test. A fault-injection exercise is successful when the system prevents or contains the unsafe action and preserves evidence, not merely when it emits an error message.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical production checklist
&lt;/h2&gt;

&lt;p&gt;Before enabling an autonomous dock integration, confirm that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;vendor API documentation matches the delivered hardware, licenses, and software version;&lt;/li&gt;
&lt;li&gt;read-only and command credentials are separated and can be rotated;&lt;/li&gt;
&lt;li&gt;every intent is immutable, versioned, expiring, and idempotent;&lt;/li&gt;
&lt;li&gt;observations include freshness and data-quality checks;&lt;/li&gt;
&lt;li&gt;command outcomes have explicit uncertain and verified states;&lt;/li&gt;
&lt;li&gt;retries require readback after any ambiguous transmission;&lt;/li&gt;
&lt;li&gt;logs are durable, redacted, time-synchronized, and reviewable;&lt;/li&gt;
&lt;li&gt;human approval covers the exact mission revision and operating window;&lt;/li&gt;
&lt;li&gt;aircraft and dock failsafes work without the external automation;&lt;/li&gt;
&lt;li&gt;controlled fault tests have been completed and defects closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The central design principle is simple: an automation system should only advance when it can prove that the required state, authorization, and prior outcome all match. Anything less becomes a stop condition. That approach may produce more visible refusals during development, but it also makes the integration understandable, testable, and safer to operate.&lt;/p&gt;

&lt;p&gt;Review the current &lt;a href="https://store.uniteduav.com/products/docking-station-uk03?utm_source=dev.to&amp;amp;utm_medium=community&amp;amp;utm_campaign=dev_uk03_api_20260828"&gt;UK03 product listing&lt;/a&gt; for the published configuration starting point, then request the applicable API documentation, supported-aircraft matrix, licensing details, and acceptance procedure for the actual deployment.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>iot</category>
      <category>robotics</category>
    </item>
  </channel>
</rss>
