fix(metadata): Update extraction logic and enhance circumference detection
- Adjusted the extract_value_raw function to return failure for unavailable values in strict mode. - Expanded the circumference detection logic in infer_unit_strict to include additional terms for better accuracy in unit inference.
This commit is contained in:
parent
fe7a69fb07
commit
8ee9fb84ba
|
|
@ -30,7 +30,8 @@ def extract_value_raw(value_display: str, output_type: OutputType, placeholder_t
|
||||||
Returns: (raw_value, success)
|
Returns: (raw_value, success)
|
||||||
"""
|
"""
|
||||||
if not value_display or value_display in ['nicht verfügbar', 'nicht genug Daten']:
|
if not value_display or value_display in ['nicht verfügbar', 'nicht genug Daten']:
|
||||||
return None, True
|
# V2 strict mode: missing/unavailable value is not a successful extraction
|
||||||
|
return None, False
|
||||||
|
|
||||||
# JSON output type
|
# JSON output type
|
||||||
if output_type == OutputType.JSON:
|
if output_type == OutputType.JSON:
|
||||||
|
|
@ -111,7 +112,8 @@ def infer_unit_strict(key: str, description: str, output_type: OutputType, place
|
||||||
return 'kg'
|
return 'kg'
|
||||||
|
|
||||||
# Circumferences/lengths
|
# Circumferences/lengths
|
||||||
if any(x in key_lower for x in ['umfang', 'waist', 'hip', 'chest', 'arm', 'leg', 'delta']) and 'circumference' in desc_lower:
|
circumference_terms = ['umfang', 'waist', 'hip', 'chest', 'arm', 'leg', 'taill', 'hueft', 'brust', 'oberarm', 'oberschenkel']
|
||||||
|
if any(x in key_lower for x in circumference_terms) or any(x in desc_lower for x in ['circumference', 'umfang', 'taill', 'hüft', 'hueft', 'brust', 'oberarm', 'oberschenkel']):
|
||||||
return 'cm'
|
return 'cm'
|
||||||
|
|
||||||
# Time durations
|
# Time durations
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,45 @@ const MODULE_LABEL = {
|
||||||
vitals_baseline: 'Vitalwerte (Baseline)',
|
vitals_baseline: 'Vitalwerte (Baseline)',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Erlaubt Eingaben wie 1,03 oder 1.03 während des Tippens; Finale normalisiert bei Blur/Speichern. */
|
||||||
|
function normalizeDecimalInputString(raw) {
|
||||||
|
let s = String(raw).trim().replace(/\s/g, '')
|
||||||
|
if (s === '') return ''
|
||||||
|
const lastComma = s.lastIndexOf(',')
|
||||||
|
const lastDot = s.lastIndexOf('.')
|
||||||
|
if (lastComma >= 0 && lastDot >= 0) {
|
||||||
|
if (lastComma > lastDot) {
|
||||||
|
s = s.replace(/\./g, '').replace(',', '.')
|
||||||
|
} else {
|
||||||
|
s = s.replace(/,/g, '')
|
||||||
|
}
|
||||||
|
} else if (lastComma >= 0) {
|
||||||
|
s = s.replace(',', '.')
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCustomFactorToTcObject(tc, fieldKey, raw) {
|
||||||
|
const base =
|
||||||
|
tc[fieldKey] && typeof tc[fieldKey] === 'object'
|
||||||
|
? { ...tc[fieldKey] }
|
||||||
|
: { type: 'float', decimal_separator: 'auto', flexible: true }
|
||||||
|
const normalized = normalizeDecimalInputString(raw)
|
||||||
|
if (normalized === '') {
|
||||||
|
delete base.conversion_factor
|
||||||
|
} else {
|
||||||
|
const num = Number(normalized)
|
||||||
|
if (Number.isNaN(num)) {
|
||||||
|
return { ok: false, message: `Konvertierungsfaktor (${fieldKey}): keine gültige Zahl.` }
|
||||||
|
}
|
||||||
|
base.conversion_factor = num
|
||||||
|
base.source_unit = 'custom'
|
||||||
|
}
|
||||||
|
delete base.target_unit
|
||||||
|
tc[fieldKey] = base
|
||||||
|
return { ok: true }
|
||||||
|
}
|
||||||
|
|
||||||
function SampleTable({ sampleRows, columns }) {
|
function SampleTable({ sampleRows, columns }) {
|
||||||
if (!sampleRows?.length || !columns?.length) return null
|
if (!sampleRows?.length || !columns?.length) return null
|
||||||
const showCols = columns.slice(0, 8)
|
const showCols = columns.slice(0, 8)
|
||||||
|
|
@ -82,6 +121,8 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
const [analyzing, setAnalyzing] = useState(false)
|
const [analyzing, setAnalyzing] = useState(false)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
|
/** Lokaler Text für Konvertierungsfaktor (nur bei source_unit custom), Commit bei Blur/Speichern — verhindert 1. → 1 beim Tippen. */
|
||||||
|
const [customFactorDraftByField, setCustomFactorDraftByField] = useState({})
|
||||||
|
|
||||||
const modMeta = useMemo(() => modules.find((m) => m.id === module), [modules, module])
|
const modMeta = useMemo(() => modules.find((m) => m.id === module), [modules, module])
|
||||||
const aggregateSleepImport = modMeta?.import_mode === 'apple_sleep_aggregate'
|
const aggregateSleepImport = modMeta?.import_mode === 'apple_sleep_aggregate'
|
||||||
|
|
@ -135,6 +176,7 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
setFieldMappings(fm)
|
setFieldMappings(fm)
|
||||||
setColumns(Object.keys(fm))
|
setColumns(Object.keys(fm))
|
||||||
setTypeConversionsText(JSON.stringify(t.type_conversions || {}, null, 2))
|
setTypeConversionsText(JSON.stringify(t.type_conversions || {}, null, 2))
|
||||||
|
setCustomFactorDraftByField({})
|
||||||
setSampleRows([])
|
setSampleRows([])
|
||||||
setSeedHint(null)
|
setSeedHint(null)
|
||||||
})
|
})
|
||||||
|
|
@ -221,6 +263,11 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
}
|
}
|
||||||
tc[fieldKey] = base
|
tc[fieldKey] = base
|
||||||
setTypeConversionsText(JSON.stringify(tc, null, 2))
|
setTypeConversionsText(JSON.stringify(tc, null, 2))
|
||||||
|
setCustomFactorDraftByField((prev) => {
|
||||||
|
const next = { ...prev }
|
||||||
|
delete next[fieldKey]
|
||||||
|
return next
|
||||||
|
})
|
||||||
setError(null)
|
setError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +283,22 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
return String(v)
|
return String(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateCustomConversionFactor = (fieldKey, raw) => {
|
const getCustomFactorFieldDisplay = (fieldKey) => {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(customFactorDraftByField, fieldKey)) {
|
||||||
|
return customFactorDraftByField[fieldKey]
|
||||||
|
}
|
||||||
|
return getCustomConversionFactorInputValue(fieldKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
const commitCustomFactorOnBlur = (fieldKey, raw) => {
|
||||||
|
if (getSourceUnitSelectValue(fieldKey) !== 'custom') {
|
||||||
|
setCustomFactorDraftByField((prev) => {
|
||||||
|
const next = { ...prev }
|
||||||
|
delete next[fieldKey]
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
let tc
|
let tc
|
||||||
try {
|
try {
|
||||||
tc = JSON.parse(typeConversionsText || '{}')
|
tc = JSON.parse(typeConversionsText || '{}')
|
||||||
|
|
@ -244,25 +306,17 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
setError('type_conversions: ungültiges JSON (Faktor kann nicht gesetzt werden).')
|
setError('type_conversions: ungültiges JSON (Faktor kann nicht gesetzt werden).')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const base =
|
const result = applyCustomFactorToTcObject(tc, fieldKey, raw)
|
||||||
tc[fieldKey] && typeof tc[fieldKey] === 'object'
|
if (!result.ok) {
|
||||||
? { ...tc[fieldKey] }
|
setError(result.message || 'Konvertierungsfaktor ungültig.')
|
||||||
: { type: 'float', decimal_separator: 'auto', flexible: true }
|
return
|
||||||
const t = String(raw).trim().replace(',', '.')
|
|
||||||
if (t === '') {
|
|
||||||
delete base.conversion_factor
|
|
||||||
} else {
|
|
||||||
const n = Number(t)
|
|
||||||
if (Number.isNaN(n)) {
|
|
||||||
setError('Konvertierungsfaktor: bitte eine gültige Zahl eingeben.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
base.conversion_factor = n
|
|
||||||
base.source_unit = 'custom'
|
|
||||||
}
|
}
|
||||||
delete base.target_unit
|
|
||||||
tc[fieldKey] = base
|
|
||||||
setTypeConversionsText(JSON.stringify(tc, null, 2))
|
setTypeConversionsText(JSON.stringify(tc, null, 2))
|
||||||
|
setCustomFactorDraftByField((prev) => {
|
||||||
|
const next = { ...prev }
|
||||||
|
delete next[fieldKey]
|
||||||
|
return next
|
||||||
|
})
|
||||||
setError(null)
|
setError(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,6 +339,7 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
setDelimiter(res.delimiter || ';')
|
setDelimiter(res.delimiter || ';')
|
||||||
setEncoding(res.encoding || 'utf-8')
|
setEncoding(res.encoding || 'utf-8')
|
||||||
setSeedHint(res.seed_template || null)
|
setSeedHint(res.seed_template || null)
|
||||||
|
setCustomFactorDraftByField({})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e.message || 'Analyse fehlgeschlagen')
|
setError(e.message || 'Analyse fehlgeschlagen')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -298,9 +353,31 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
setError(null)
|
setError(null)
|
||||||
|
let textForTc = typeConversionsText
|
||||||
|
const pendingFactorDrafts = { ...customFactorDraftByField }
|
||||||
|
if (Object.keys(pendingFactorDrafts).length > 0) {
|
||||||
|
try {
|
||||||
|
const tco = JSON.parse(textForTc || '{}')
|
||||||
|
for (const [fk, raw] of Object.entries(pendingFactorDrafts)) {
|
||||||
|
const su = String(tco[fk]?.source_unit || '').toLowerCase()
|
||||||
|
if (su !== 'custom') continue
|
||||||
|
const result = applyCustomFactorToTcObject(tco, fk, raw)
|
||||||
|
if (!result.ok) {
|
||||||
|
setError(result.message || 'Konvertierungsfaktor ungültig.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textForTc = JSON.stringify(tco, null, 2)
|
||||||
|
setTypeConversionsText(textForTc)
|
||||||
|
setCustomFactorDraftByField({})
|
||||||
|
} catch {
|
||||||
|
setError('type_conversions: ungültiges JSON.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
let tc = null
|
let tc = null
|
||||||
try {
|
try {
|
||||||
tc = JSON.parse(typeConversionsText || '{}')
|
tc = JSON.parse(textForTc || '{}')
|
||||||
if (tc !== null && typeof tc !== 'object') throw new Error()
|
if (tc !== null && typeof tc !== 'object') throw new Error()
|
||||||
} catch {
|
} catch {
|
||||||
setError('type_conversions: ungültiges JSON.')
|
setError('type_conversions: ungültiges JSON.')
|
||||||
|
|
@ -682,12 +759,18 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="decimal"
|
inputMode="decimal"
|
||||||
className="form-input"
|
className="form-input"
|
||||||
placeholder="z. B. 1.03 bei ml→g (Dichte ≈ 1,03 g/ml)"
|
placeholder="z. B. 1,03 oder 1.03 (ml→g nach Dichte)"
|
||||||
value={getCustomConversionFactorInputValue(fkey)}
|
value={getCustomFactorFieldDisplay(fkey)}
|
||||||
onChange={(e) => updateCustomConversionFactor(fkey, e.target.value)}
|
onChange={(e) =>
|
||||||
|
setCustomFactorDraftByField((prev) => ({ ...prev, [fkey]: e.target.value }))
|
||||||
|
}
|
||||||
|
onBlur={(e) => commitCustomFactorOnBlur(fkey, e.target.value)}
|
||||||
style={{ width: '100%', textAlign: 'left' }}
|
style={{ width: '100%', textAlign: 'left' }}
|
||||||
/>
|
/>
|
||||||
{getCustomConversionFactorInputValue(fkey) === '' && (
|
<p style={{ fontSize: 12, color: 'var(--text3)', marginTop: 6 }}>
|
||||||
|
Dezimalkomma oder -punkt; mit Tab oder Klick außerhalb übernehmen (oder direkt Speichern).
|
||||||
|
</p>
|
||||||
|
{getCustomFactorFieldDisplay(fkey) === '' && (
|
||||||
<p style={{ fontSize: 12, color: 'var(--text3)', marginTop: 6 }}>
|
<p style={{ fontSize: 12, color: 'var(--text3)', marginTop: 6 }}>
|
||||||
Ohne Faktor gilt keine zusätzliche Skalierung (wie 1:1 nach dem Parsen).
|
Ohne Faktor gilt keine zusätzliche Skalierung (wie 1:1 nach dem Parsen).
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -720,7 +803,10 @@ export default function AdminCsvTemplateEditorPage() {
|
||||||
textAlign: 'left',
|
textAlign: 'left',
|
||||||
}}
|
}}
|
||||||
value={typeConversionsText}
|
value={typeConversionsText}
|
||||||
onChange={(e) => setTypeConversionsText(e.target.value)}
|
onChange={(e) => {
|
||||||
|
setTypeConversionsText(e.target.value)
|
||||||
|
setCustomFactorDraftByField({})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user